@nextclaw/kernel 0.2.3 → 0.2.5
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/dist/index.d.ts +97 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +422 -39
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { n as getUnsignedUpdateManifest, r as serializeUnsignedUpdateManifest, t as UpdateManifestReader } from "./update-manifest.types-C0qPrjGQ.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { NcpEventType, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
|
|
4
|
-
import { AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ChannelManager, ChannelManager as ChannelManager$1, ConfigSchema, ContextBuilder, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, EditFileTool, ExecTool, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SessionSearchManager, SkillsLoader, THINKING_LEVELS, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createTypingStopControlMessage, diffConfigPaths, ensureDir, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeInlineSecretRefs, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, summarizeSessionRequestTask, toDisposable, toExtensionConfigView } from "@nextclaw/core";
|
|
4
|
+
import { AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ChannelManager, ChannelManager as ChannelManager$1, ConfigSchema, ContextBuilder, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, EditFileTool, ExecTool, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SessionSearchManager, SkillsLoader, THINKING_LEVELS, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createTypingStopControlMessage, diffConfigPaths, ensureDir, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeInlineSecretRefs, normalizeProviderModelConfig, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, summarizeSessionRequestTask, toDisposable, toExtensionConfigView } from "@nextclaw/core";
|
|
5
5
|
import { LocalAssetStore, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, ncpMessageToOpenAiMessages, validateToolArgs } from "@nextclaw/ncp-agent-runtime";
|
|
6
|
-
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { createHash, createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
7
7
|
import { EventBus, Ingress, eventKeys, ingressKeys } from "@nextclaw/shared";
|
|
8
8
|
import { catchError, from, lastValueFrom, tap } from "rxjs";
|
|
9
|
-
import { appendFileSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { appendFileSync, chmodSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
10
11
|
import { spawn } from "node:child_process";
|
|
11
12
|
import { access, appendFile, mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
12
|
-
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
|
|
15
15
|
import { McpRegistryService, McpServerLifecycleManager } from "@nextclaw/mcp";
|
|
@@ -887,6 +887,308 @@ var AgentRuntimeManager = class {
|
|
|
887
887
|
};
|
|
888
888
|
};
|
|
889
889
|
//#endregion
|
|
890
|
+
//#region src/utils/access-token.utils.ts
|
|
891
|
+
function createAccessSessionToken() {
|
|
892
|
+
return randomBytes(32).toString("base64url");
|
|
893
|
+
}
|
|
894
|
+
function hashAccessSessionToken(token) {
|
|
895
|
+
return createHash("sha256").update(token).digest("hex");
|
|
896
|
+
}
|
|
897
|
+
//#endregion
|
|
898
|
+
//#region src/stores/access-session.store.ts
|
|
899
|
+
const ACCESS_SESSION_STATE_VERSION = 1;
|
|
900
|
+
const ACCESS_SESSION_STATE_KIND = "nextclaw.access.sessions";
|
|
901
|
+
function createEmptyState() {
|
|
902
|
+
return {
|
|
903
|
+
kind: ACCESS_SESSION_STATE_KIND,
|
|
904
|
+
version: ACCESS_SESSION_STATE_VERSION,
|
|
905
|
+
sessions: []
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function isAccessPrincipal(value) {
|
|
909
|
+
if (!value || typeof value !== "object") return false;
|
|
910
|
+
const record = value;
|
|
911
|
+
return typeof record.id === "string" && record.id.trim().length > 0 && record.role === "admin";
|
|
912
|
+
}
|
|
913
|
+
function parseAccessSessionRecord(value) {
|
|
914
|
+
if (!value || typeof value !== "object") return null;
|
|
915
|
+
const record = value;
|
|
916
|
+
if (typeof record.tokenHash !== "string" || !record.tokenHash.trim() || !isAccessPrincipal(record.principal) || typeof record.createdAt !== "string" || typeof record.expiresAt !== "string") return null;
|
|
917
|
+
return {
|
|
918
|
+
tokenHash: record.tokenHash,
|
|
919
|
+
principal: record.principal,
|
|
920
|
+
createdAt: record.createdAt,
|
|
921
|
+
expiresAt: record.expiresAt
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function parseAccessSessionState(value) {
|
|
925
|
+
if (!value || typeof value !== "object") return createEmptyState();
|
|
926
|
+
const record = value;
|
|
927
|
+
if (record.kind !== ACCESS_SESSION_STATE_KIND || record.version !== ACCESS_SESSION_STATE_VERSION || !Array.isArray(record.sessions)) return createEmptyState();
|
|
928
|
+
return {
|
|
929
|
+
kind: ACCESS_SESSION_STATE_KIND,
|
|
930
|
+
version: ACCESS_SESSION_STATE_VERSION,
|
|
931
|
+
sessions: record.sessions.map(parseAccessSessionRecord).filter((session) => session !== null)
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function isSessionActive(session, nowMs) {
|
|
935
|
+
const expiresAtMs = Date.parse(session.expiresAt);
|
|
936
|
+
return Number.isFinite(expiresAtMs) && expiresAtMs > nowMs;
|
|
937
|
+
}
|
|
938
|
+
var AccessSessionStore = class {
|
|
939
|
+
constructor(storePath) {
|
|
940
|
+
this.storePath = storePath;
|
|
941
|
+
}
|
|
942
|
+
findPrincipalByToken = (token, now) => {
|
|
943
|
+
const normalizedToken = token?.trim();
|
|
944
|
+
if (!normalizedToken) return null;
|
|
945
|
+
const tokenHash = hashAccessSessionToken(normalizedToken);
|
|
946
|
+
const nowMs = now.getTime();
|
|
947
|
+
return this.readState().sessions.find((entry) => entry.tokenHash === tokenHash && isSessionActive(entry, nowMs))?.principal ?? null;
|
|
948
|
+
};
|
|
949
|
+
saveSession = (session, now) => {
|
|
950
|
+
const activeSessions = this.readState().sessions.filter((entry) => entry.tokenHash !== session.tokenHash && isSessionActive(entry, now.getTime()));
|
|
951
|
+
this.writeState({
|
|
952
|
+
kind: ACCESS_SESSION_STATE_KIND,
|
|
953
|
+
version: ACCESS_SESSION_STATE_VERSION,
|
|
954
|
+
sessions: [...activeSessions, session]
|
|
955
|
+
});
|
|
956
|
+
};
|
|
957
|
+
deleteToken = (token) => {
|
|
958
|
+
const normalizedToken = token?.trim();
|
|
959
|
+
if (!normalizedToken) return;
|
|
960
|
+
const tokenHash = hashAccessSessionToken(normalizedToken);
|
|
961
|
+
this.writeState({
|
|
962
|
+
kind: ACCESS_SESSION_STATE_KIND,
|
|
963
|
+
version: ACCESS_SESSION_STATE_VERSION,
|
|
964
|
+
sessions: this.readState().sessions.filter((session) => session.tokenHash !== tokenHash)
|
|
965
|
+
});
|
|
966
|
+
};
|
|
967
|
+
clear = () => {
|
|
968
|
+
this.writeState(createEmptyState());
|
|
969
|
+
};
|
|
970
|
+
readState = () => {
|
|
971
|
+
if (!existsSync(this.storePath)) return createEmptyState();
|
|
972
|
+
try {
|
|
973
|
+
return parseAccessSessionState(JSON.parse(readFileSync(this.storePath, "utf-8")));
|
|
974
|
+
} catch {
|
|
975
|
+
return createEmptyState();
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
writeState = (state) => {
|
|
979
|
+
mkdirSync(dirname(this.storePath), { recursive: true });
|
|
980
|
+
const temporaryPath = `${this.storePath}.${process.pid}.tmp`;
|
|
981
|
+
writeFileSync(temporaryPath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
982
|
+
renameSync(temporaryPath, this.storePath);
|
|
983
|
+
chmodSync(this.storePath, 384);
|
|
984
|
+
};
|
|
985
|
+
};
|
|
986
|
+
//#endregion
|
|
987
|
+
//#region src/utils/access-password.utils.ts
|
|
988
|
+
function createAccessPasswordRecord(password) {
|
|
989
|
+
const passwordSalt = randomBytes(16).toString("hex");
|
|
990
|
+
return {
|
|
991
|
+
passwordHash: scryptAccessPassword(password, passwordSalt),
|
|
992
|
+
passwordSalt
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function verifyAccessPassword(password, expectedHash, salt) {
|
|
996
|
+
const actualHashBuffer = Buffer.from(scryptAccessPassword(password, salt), "hex");
|
|
997
|
+
const expectedHashBuffer = Buffer.from(expectedHash, "hex");
|
|
998
|
+
if (actualHashBuffer.length !== expectedHashBuffer.length) return false;
|
|
999
|
+
return timingSafeEqual(actualHashBuffer, expectedHashBuffer);
|
|
1000
|
+
}
|
|
1001
|
+
function scryptAccessPassword(password, salt) {
|
|
1002
|
+
return scryptSync(password, salt, 64).toString("hex");
|
|
1003
|
+
}
|
|
1004
|
+
//#endregion
|
|
1005
|
+
//#region src/managers/access.manager.ts
|
|
1006
|
+
const PASSWORD_MIN_LENGTH = 8;
|
|
1007
|
+
const DEFAULT_ACCESS_SESSION_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
1008
|
+
function normalizeUsername(value) {
|
|
1009
|
+
return value.trim();
|
|
1010
|
+
}
|
|
1011
|
+
function validateUsernameAndPassword(username, password) {
|
|
1012
|
+
if (!username) throw new Error("Username is required.");
|
|
1013
|
+
if (password.trim().length < PASSWORD_MIN_LENGTH) throw new Error(`Password must be at least ${PASSWORD_MIN_LENGTH} characters.`);
|
|
1014
|
+
}
|
|
1015
|
+
function resolveAccessSessionStorePath(options) {
|
|
1016
|
+
const { configPath, homeDir, sessionStorePath } = options;
|
|
1017
|
+
if (sessionStorePath?.trim()) return sessionStorePath.trim();
|
|
1018
|
+
if (homeDir?.trim()) return resolve(homeDir.trim(), "access", "access-sessions.json");
|
|
1019
|
+
if (configPath?.trim()) return resolve(dirname(configPath.trim()), "access", "access-sessions.json");
|
|
1020
|
+
return resolve(getDataDir(), "access", "access-sessions.json");
|
|
1021
|
+
}
|
|
1022
|
+
var AccessManager = class {
|
|
1023
|
+
sessions;
|
|
1024
|
+
configPath;
|
|
1025
|
+
sessionTtlMs;
|
|
1026
|
+
now;
|
|
1027
|
+
constructor(options = {}) {
|
|
1028
|
+
this.options = options;
|
|
1029
|
+
this.configPath = options.configManager?.configPath ?? options.configPath ?? getConfigPath();
|
|
1030
|
+
this.sessions = new AccessSessionStore(resolveAccessSessionStorePath({
|
|
1031
|
+
...options,
|
|
1032
|
+
configPath: this.configPath
|
|
1033
|
+
}));
|
|
1034
|
+
this.sessionTtlMs = Math.max(1, Math.trunc(options.sessionTtlMs ?? DEFAULT_ACCESS_SESSION_TTL_MS));
|
|
1035
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1036
|
+
}
|
|
1037
|
+
isPasswordProtectionEnabled = () => {
|
|
1038
|
+
const auth = this.readAuthConfig();
|
|
1039
|
+
return Boolean(auth.enabled && this.isConfigured(auth));
|
|
1040
|
+
};
|
|
1041
|
+
authenticateSession = (token) => {
|
|
1042
|
+
const auth = this.readAuthConfig();
|
|
1043
|
+
if (!auth.enabled || !this.isConfigured(auth)) return {
|
|
1044
|
+
id: "admin",
|
|
1045
|
+
role: "admin"
|
|
1046
|
+
};
|
|
1047
|
+
return this.sessions.findPrincipalByToken(token, this.now());
|
|
1048
|
+
};
|
|
1049
|
+
getPasswordAuthStatus = (token) => {
|
|
1050
|
+
const auth = this.readAuthConfig();
|
|
1051
|
+
const configured = this.isConfigured(auth);
|
|
1052
|
+
const enabled = Boolean(auth.enabled && configured);
|
|
1053
|
+
const username = configured ? normalizeUsername(auth.username) : void 0;
|
|
1054
|
+
return {
|
|
1055
|
+
enabled,
|
|
1056
|
+
configured,
|
|
1057
|
+
authenticated: enabled ? this.sessions.findPrincipalByToken(token, this.now()) !== null : false,
|
|
1058
|
+
username
|
|
1059
|
+
};
|
|
1060
|
+
};
|
|
1061
|
+
setupPasswordAdmin = async (input) => {
|
|
1062
|
+
const config = this.loadCurrentConfig();
|
|
1063
|
+
const currentAuth = config.ui.auth;
|
|
1064
|
+
if (this.isConfigured(currentAuth)) throw new Error("UI authentication is already configured.");
|
|
1065
|
+
const username = normalizeUsername(input.username);
|
|
1066
|
+
validateUsernameAndPassword(username, input.password);
|
|
1067
|
+
const nextPassword = createAccessPasswordRecord(input.password);
|
|
1068
|
+
config.ui.auth = {
|
|
1069
|
+
enabled: true,
|
|
1070
|
+
username,
|
|
1071
|
+
passwordHash: nextPassword.passwordHash,
|
|
1072
|
+
passwordSalt: nextPassword.passwordSalt
|
|
1073
|
+
};
|
|
1074
|
+
await this.saveCurrentConfig(config, "setup password access");
|
|
1075
|
+
this.sessions.clear();
|
|
1076
|
+
return this.createLoginResult(username, true);
|
|
1077
|
+
};
|
|
1078
|
+
loginWithPassword = (input) => {
|
|
1079
|
+
const auth = this.readAuthConfig();
|
|
1080
|
+
if (!auth.enabled || !this.isConfigured(auth)) throw new Error("UI authentication is not enabled.");
|
|
1081
|
+
const username = normalizeUsername(input.username);
|
|
1082
|
+
if (username !== normalizeUsername(auth.username) || !verifyAccessPassword(input.password, auth.passwordHash, auth.passwordSalt)) throw new Error("Invalid username or password.");
|
|
1083
|
+
return this.createLoginResult(username, true);
|
|
1084
|
+
};
|
|
1085
|
+
logout = (token) => {
|
|
1086
|
+
this.sessions.deleteToken(token);
|
|
1087
|
+
};
|
|
1088
|
+
updatePassword = async (input) => {
|
|
1089
|
+
const config = this.loadCurrentConfig();
|
|
1090
|
+
const auth = config.ui.auth;
|
|
1091
|
+
if (!this.isConfigured(auth)) throw new Error("UI authentication is not configured.");
|
|
1092
|
+
if (auth.enabled && this.sessions.findPrincipalByToken(input.token, this.now()) === null) throw new Error("Authentication required.");
|
|
1093
|
+
const username = normalizeUsername(auth.username);
|
|
1094
|
+
validateUsernameAndPassword(username, input.password);
|
|
1095
|
+
const nextPassword = createAccessPasswordRecord(input.password);
|
|
1096
|
+
config.ui.auth = {
|
|
1097
|
+
enabled: auth.enabled,
|
|
1098
|
+
username: auth.username,
|
|
1099
|
+
passwordHash: nextPassword.passwordHash,
|
|
1100
|
+
passwordSalt: nextPassword.passwordSalt
|
|
1101
|
+
};
|
|
1102
|
+
await this.saveCurrentConfig(config, "update password access");
|
|
1103
|
+
this.sessions.clear();
|
|
1104
|
+
if (!auth.enabled) return { status: {
|
|
1105
|
+
enabled: false,
|
|
1106
|
+
configured: true,
|
|
1107
|
+
authenticated: false,
|
|
1108
|
+
username
|
|
1109
|
+
} };
|
|
1110
|
+
return this.createLoginResult(username, true);
|
|
1111
|
+
};
|
|
1112
|
+
setPasswordAuthEnabled = async (input) => {
|
|
1113
|
+
const config = this.loadCurrentConfig();
|
|
1114
|
+
const auth = config.ui.auth;
|
|
1115
|
+
const configured = this.isConfigured(auth);
|
|
1116
|
+
if (Boolean(auth.enabled && configured) && this.sessions.findPrincipalByToken(input.token, this.now()) === null) throw new Error("Authentication required.");
|
|
1117
|
+
if (input.enabled && !configured) throw new Error("UI authentication must be configured before it can be enabled.");
|
|
1118
|
+
config.ui.auth = {
|
|
1119
|
+
enabled: Boolean(input.enabled),
|
|
1120
|
+
username: auth.username,
|
|
1121
|
+
passwordHash: auth.passwordHash,
|
|
1122
|
+
passwordSalt: auth.passwordSalt
|
|
1123
|
+
};
|
|
1124
|
+
await this.saveCurrentConfig(config, "update password access enabled");
|
|
1125
|
+
if (!input.enabled) {
|
|
1126
|
+
this.sessions.clear();
|
|
1127
|
+
return { status: {
|
|
1128
|
+
enabled: false,
|
|
1129
|
+
configured,
|
|
1130
|
+
authenticated: false,
|
|
1131
|
+
username: configured ? normalizeUsername(auth.username) : void 0
|
|
1132
|
+
} };
|
|
1133
|
+
}
|
|
1134
|
+
return this.createLoginResult(normalizeUsername(auth.username), true);
|
|
1135
|
+
};
|
|
1136
|
+
createTrustedSession = () => {
|
|
1137
|
+
const auth = this.readAuthConfig();
|
|
1138
|
+
if (!auth.enabled || !this.isConfigured(auth)) return null;
|
|
1139
|
+
return this.createLoginResult(normalizeUsername(auth.username), true);
|
|
1140
|
+
};
|
|
1141
|
+
loadCurrentConfig = () => {
|
|
1142
|
+
return this.options.configManager?.loadConfig() ?? loadConfig(this.configPath);
|
|
1143
|
+
};
|
|
1144
|
+
saveCurrentConfig = async (config, note) => {
|
|
1145
|
+
const parsed = ConfigSchema.parse(config);
|
|
1146
|
+
if (this.options.configManager) {
|
|
1147
|
+
await this.options.configManager.applyConfig(parsed, note);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
saveConfig(parsed, this.configPath);
|
|
1151
|
+
};
|
|
1152
|
+
readAuthConfig = () => {
|
|
1153
|
+
return this.loadCurrentConfig().ui.auth;
|
|
1154
|
+
};
|
|
1155
|
+
isConfigured = (auth) => {
|
|
1156
|
+
return Boolean(normalizeUsername(auth.username).length > 0 && auth.passwordHash.trim().length > 0 && auth.passwordSalt.trim().length > 0);
|
|
1157
|
+
};
|
|
1158
|
+
createLoginResult = (username, authenticated) => {
|
|
1159
|
+
const session = this.createSession(username);
|
|
1160
|
+
return {
|
|
1161
|
+
status: {
|
|
1162
|
+
enabled: true,
|
|
1163
|
+
configured: true,
|
|
1164
|
+
authenticated,
|
|
1165
|
+
username
|
|
1166
|
+
},
|
|
1167
|
+
token: session.token,
|
|
1168
|
+
expiresAt: session.record.expiresAt
|
|
1169
|
+
};
|
|
1170
|
+
};
|
|
1171
|
+
createSession = (username) => {
|
|
1172
|
+
const token = createAccessSessionToken();
|
|
1173
|
+
const now = this.now();
|
|
1174
|
+
const expiresAt = new Date(now.getTime() + this.sessionTtlMs).toISOString();
|
|
1175
|
+
const record = {
|
|
1176
|
+
tokenHash: hashAccessSessionToken(token),
|
|
1177
|
+
principal: {
|
|
1178
|
+
id: username,
|
|
1179
|
+
role: "admin"
|
|
1180
|
+
},
|
|
1181
|
+
createdAt: now.toISOString(),
|
|
1182
|
+
expiresAt
|
|
1183
|
+
};
|
|
1184
|
+
this.sessions.saveSession(record, now);
|
|
1185
|
+
return {
|
|
1186
|
+
token,
|
|
1187
|
+
record
|
|
1188
|
+
};
|
|
1189
|
+
};
|
|
1190
|
+
};
|
|
1191
|
+
//#endregion
|
|
890
1192
|
//#region src/managers/automation.manager.ts
|
|
891
1193
|
var AutomationManager = class extends CronService {
|
|
892
1194
|
constructor(options) {
|
|
@@ -993,6 +1295,10 @@ var ConfigManager = class {
|
|
|
993
1295
|
getConfigSchema = (params = {}) => buildConfigSchema({ version: params.version });
|
|
994
1296
|
applyRawConfig = async (params) => this.mutateRawConfig(params, (_snapshot, parsed) => parsed);
|
|
995
1297
|
patchRawConfig = async (params) => this.mutateRawConfig(params, (snapshot, patch) => mergeDeep(snapshot.config, patch));
|
|
1298
|
+
applyConfig = async (nextConfig, note) => this.applyConfigChange({
|
|
1299
|
+
nextConfig,
|
|
1300
|
+
note
|
|
1301
|
+
});
|
|
996
1302
|
scheduleReload = (reason) => {
|
|
997
1303
|
if (this.reloadTimer) clearTimeout(this.reloadTimer);
|
|
998
1304
|
this.reloadTimer = setTimeout(() => {
|
|
@@ -1577,6 +1883,7 @@ var ExtensionLifecycleService = class {
|
|
|
1577
1883
|
//#endregion
|
|
1578
1884
|
//#region src/features/extension-runtime/services/extension-manifest-discovery.service.ts
|
|
1579
1885
|
const EXTENSION_MANIFEST_FILE = "nextclaw.extension.json";
|
|
1886
|
+
const PACKAGED_EXTENSION_DIR_ENV = "NEXTCLAW_PACKAGED_EXTENSION_DIR";
|
|
1580
1887
|
const BUILTIN_EXTENSION_PACKAGES = [
|
|
1581
1888
|
"@nextclaw/channel-extension-dingtalk",
|
|
1582
1889
|
"@nextclaw/channel-extension-discord",
|
|
@@ -1667,12 +1974,17 @@ function resolveBuiltinExtensionManifestRoots() {
|
|
|
1667
1974
|
} catch {}
|
|
1668
1975
|
return uniquePaths(roots);
|
|
1669
1976
|
}
|
|
1977
|
+
function resolvePackagedExtensionManifestRoots(explicitDir = process.env[PACKAGED_EXTENSION_DIR_ENV]) {
|
|
1978
|
+
const configured = explicitDir?.trim();
|
|
1979
|
+
return configured ? [resolve(configured)] : [];
|
|
1980
|
+
}
|
|
1670
1981
|
function resolveExtensionManifestRoots(params) {
|
|
1671
1982
|
const devExtensionsDir = resolveDevFirstPartyExtensionDir();
|
|
1672
1983
|
return uniquePaths([
|
|
1673
1984
|
join(getDataPath(), "extensions"),
|
|
1674
1985
|
join(params.workspace, ".nextclaw", "extensions"),
|
|
1675
1986
|
...devExtensionsDir ? [devExtensionsDir] : [],
|
|
1987
|
+
...resolvePackagedExtensionManifestRoots(),
|
|
1676
1988
|
...resolveBuiltinExtensionManifestRoots()
|
|
1677
1989
|
]);
|
|
1678
1990
|
}
|
|
@@ -2297,8 +2609,10 @@ var LlmProviderManager = class {
|
|
|
2297
2609
|
testConnection = async (input) => {
|
|
2298
2610
|
await this.createProvider({
|
|
2299
2611
|
providerName: input.providerName,
|
|
2612
|
+
providerId: input.providerName,
|
|
2300
2613
|
provider: {
|
|
2301
2614
|
enabled: true,
|
|
2615
|
+
providerType: input.providerName,
|
|
2302
2616
|
displayName: "",
|
|
2303
2617
|
apiKey: input.apiKey ?? "",
|
|
2304
2618
|
apiBase: input.apiBase ?? null,
|
|
@@ -2319,90 +2633,131 @@ var LlmProviderManager = class {
|
|
|
2319
2633
|
resolveRoute = (model) => {
|
|
2320
2634
|
if (!this.config) return null;
|
|
2321
2635
|
const effectiveModel = normalizedModel(model) ?? this.config.agents.defaults.model;
|
|
2322
|
-
const
|
|
2636
|
+
const route = this.resolveProvider(effectiveModel);
|
|
2637
|
+
const { provider, name } = route;
|
|
2323
2638
|
const providerSpec = name ? this.providerRegistry.findProviderByName(name) : void 0;
|
|
2639
|
+
const specModelConfig = normalizeProviderModelConfig(providerSpec?.modelConfig ?? {});
|
|
2640
|
+
const providerModelConfig = this.normalizeProviderModelConfigForRoute(provider?.modelConfig ?? {}, route.providerId, name);
|
|
2324
2641
|
return {
|
|
2325
|
-
model:
|
|
2642
|
+
model: route.model,
|
|
2643
|
+
providerId: route.providerId,
|
|
2326
2644
|
providerName: name,
|
|
2327
2645
|
provider,
|
|
2328
|
-
apiBase: provider?.apiBase ?? providerSpec?.defaultApiBase ?? null
|
|
2646
|
+
apiBase: provider?.apiBase ?? providerSpec?.defaultApiBase ?? null,
|
|
2647
|
+
modelConfig: {
|
|
2648
|
+
...specModelConfig,
|
|
2649
|
+
...providerModelConfig
|
|
2650
|
+
}
|
|
2329
2651
|
};
|
|
2330
2652
|
};
|
|
2331
2653
|
resolveProvider = (model) => {
|
|
2332
2654
|
const providers = this.config?.providers;
|
|
2333
2655
|
if (!providers) return {
|
|
2334
2656
|
provider: null,
|
|
2335
|
-
name: null
|
|
2657
|
+
name: null,
|
|
2658
|
+
providerId: null,
|
|
2659
|
+
model
|
|
2336
2660
|
};
|
|
2337
2661
|
const specs = this.providerRegistry.listProviderSpecs();
|
|
2338
2662
|
const modelLower = model.toLowerCase();
|
|
2339
2663
|
const modelPrefix = modelLower.includes("/") ? modelLower.slice(0, modelLower.indexOf("/")) : "";
|
|
2340
2664
|
if (modelPrefix) {
|
|
2341
|
-
const prefixed = this.matchPrefixedProvider(providers, modelPrefix);
|
|
2665
|
+
const prefixed = this.matchPrefixedProvider(providers, modelPrefix, model);
|
|
2342
2666
|
if (prefixed) return prefixed;
|
|
2343
2667
|
}
|
|
2344
|
-
const keywordMatches =
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2668
|
+
const keywordMatches = Object.entries(providers).map(([providerId, provider]) => {
|
|
2669
|
+
const providerType = this.resolveProviderType(providerId, provider);
|
|
2670
|
+
return {
|
|
2671
|
+
name: providerType,
|
|
2672
|
+
providerId,
|
|
2673
|
+
provider,
|
|
2674
|
+
spec: providerType ? this.providerRegistry.findProviderByName(providerType) : void 0
|
|
2675
|
+
};
|
|
2676
|
+
}).filter((entry) => Boolean(entry.name && entry.spec)).filter((entry) => entry.provider?.enabled !== false && Boolean(entry.provider?.apiKey)).filter((entry) => entry.spec.keywords.some((keyword) => modelLower.includes(keyword)));
|
|
2349
2677
|
if (keywordMatches.length === 1) {
|
|
2350
2678
|
const match = keywordMatches[0];
|
|
2679
|
+
const providerId = match.providerId;
|
|
2351
2680
|
return {
|
|
2352
2681
|
provider: match.provider ?? null,
|
|
2353
|
-
name: match.name
|
|
2682
|
+
name: match.name,
|
|
2683
|
+
providerId,
|
|
2684
|
+
model: this.rewriteModelForTemplate(model, providerId, match.name)
|
|
2354
2685
|
};
|
|
2355
2686
|
}
|
|
2356
2687
|
if (keywordMatches.length > 1) return {
|
|
2357
2688
|
provider: null,
|
|
2358
|
-
name: null
|
|
2689
|
+
name: null,
|
|
2690
|
+
providerId: null,
|
|
2691
|
+
model
|
|
2359
2692
|
};
|
|
2360
2693
|
const builtinNames = new Set(specs.map((spec) => spec.name));
|
|
2361
2694
|
const enabledProviders = Object.entries(providers).filter(([, provider]) => provider.enabled !== false && Boolean(provider.apiKey));
|
|
2362
2695
|
const enabledBuiltin = enabledProviders.filter(([name]) => builtinNames.has(name));
|
|
2363
2696
|
if (enabledBuiltin.length === 1) {
|
|
2364
2697
|
const [name, provider] = enabledBuiltin[0];
|
|
2698
|
+
const providerType = this.resolveProviderType(name, provider);
|
|
2365
2699
|
return {
|
|
2366
2700
|
provider,
|
|
2367
|
-
name
|
|
2701
|
+
name: providerType,
|
|
2702
|
+
providerId: name,
|
|
2703
|
+
model: this.rewriteModelForTemplate(model, name, providerType)
|
|
2368
2704
|
};
|
|
2369
2705
|
}
|
|
2370
2706
|
const enabledCustom = enabledProviders.filter(([name]) => !builtinNames.has(name));
|
|
2371
2707
|
if (enabledCustom.length === 1) {
|
|
2372
2708
|
const [name, provider] = enabledCustom[0];
|
|
2709
|
+
const providerType = this.resolveProviderType(name, provider);
|
|
2373
2710
|
return {
|
|
2374
2711
|
provider,
|
|
2375
|
-
name
|
|
2712
|
+
name: providerType,
|
|
2713
|
+
providerId: name,
|
|
2714
|
+
model: this.rewriteModelForTemplate(model, name, providerType)
|
|
2376
2715
|
};
|
|
2377
2716
|
}
|
|
2378
2717
|
return {
|
|
2379
2718
|
provider: null,
|
|
2380
|
-
name: null
|
|
2719
|
+
name: null,
|
|
2720
|
+
providerId: null,
|
|
2721
|
+
model
|
|
2381
2722
|
};
|
|
2382
2723
|
};
|
|
2383
|
-
matchPrefixedProvider = (providers, modelPrefix) => {
|
|
2384
|
-
for (const spec of this.providerRegistry.listProviderSpecs()) {
|
|
2385
|
-
const provider = providers[spec.name];
|
|
2386
|
-
const aliases = [spec.name, spec.modelPrefix ?? ""].map((value) => value.trim().toLowerCase()).filter(Boolean);
|
|
2387
|
-
if (provider && aliases.includes(modelPrefix)) return provider.enabled !== false ? {
|
|
2388
|
-
provider,
|
|
2389
|
-
name: spec.name
|
|
2390
|
-
} : {
|
|
2391
|
-
provider: null,
|
|
2392
|
-
name: null
|
|
2393
|
-
};
|
|
2394
|
-
}
|
|
2724
|
+
matchPrefixedProvider = (providers, modelPrefix, model) => {
|
|
2395
2725
|
const customProvider = Object.entries(providers).find(([name]) => name.toLowerCase() === modelPrefix);
|
|
2396
2726
|
if (!customProvider) return null;
|
|
2397
|
-
const [
|
|
2398
|
-
|
|
2399
|
-
provider,
|
|
2400
|
-
name
|
|
2401
|
-
} : {
|
|
2727
|
+
const [providerId, provider] = customProvider;
|
|
2728
|
+
if (provider.enabled === false) return {
|
|
2402
2729
|
provider: null,
|
|
2403
|
-
name: null
|
|
2730
|
+
name: null,
|
|
2731
|
+
providerId,
|
|
2732
|
+
model
|
|
2733
|
+
};
|
|
2734
|
+
const providerType = this.resolveProviderType(providerId, provider);
|
|
2735
|
+
return {
|
|
2736
|
+
provider,
|
|
2737
|
+
name: providerType,
|
|
2738
|
+
providerId,
|
|
2739
|
+
model: this.rewriteModelForTemplate(model, providerId, providerType)
|
|
2404
2740
|
};
|
|
2405
2741
|
};
|
|
2742
|
+
resolveProviderType = (providerId, provider) => {
|
|
2743
|
+
const configuredType = typeof provider.providerType === "string" ? provider.providerType.trim() : "";
|
|
2744
|
+
if (configuredType && this.providerRegistry.findProviderByName(configuredType)) return configuredType;
|
|
2745
|
+
return this.providerRegistry.findProviderByName(providerId) ? providerId : null;
|
|
2746
|
+
};
|
|
2747
|
+
rewriteModelForTemplate = (model, providerId, providerType) => {
|
|
2748
|
+
const prefix = `${providerId}/`;
|
|
2749
|
+
if (!model.startsWith(prefix)) return model;
|
|
2750
|
+
const suffix = model.slice(prefix.length).trim();
|
|
2751
|
+
if (!suffix) return model;
|
|
2752
|
+
return providerType ? `${providerType}/${suffix}` : suffix;
|
|
2753
|
+
};
|
|
2754
|
+
normalizeProviderModelConfigForRoute = (modelConfig, providerId, providerType) => {
|
|
2755
|
+
const normalized = normalizeProviderModelConfig(modelConfig ?? {});
|
|
2756
|
+
if (!providerId || !providerType) return normalized;
|
|
2757
|
+
const rewritten = {};
|
|
2758
|
+
for (const [model, config] of Object.entries(normalized)) rewritten[this.rewriteModelForTemplate(model, providerId, providerType)] = config;
|
|
2759
|
+
return rewritten;
|
|
2760
|
+
};
|
|
2406
2761
|
getOrCreateProvider = (route) => {
|
|
2407
2762
|
if (!route.provider?.apiKey && !route.model.startsWith("bedrock/")) return this.missingProvider;
|
|
2408
2763
|
const cacheKey = this.buildCacheKey(route);
|
|
@@ -2429,7 +2784,7 @@ var LlmProviderManager = class {
|
|
|
2429
2784
|
supportsVision: modelSupportsVision({
|
|
2430
2785
|
model: route?.model,
|
|
2431
2786
|
providerName: route?.providerName,
|
|
2432
|
-
modelConfig: route?.
|
|
2787
|
+
modelConfig: route?.modelConfig
|
|
2433
2788
|
})
|
|
2434
2789
|
});
|
|
2435
2790
|
};
|
|
@@ -2437,6 +2792,7 @@ var LlmProviderManager = class {
|
|
|
2437
2792
|
const routeProvider = route.provider;
|
|
2438
2793
|
return [
|
|
2439
2794
|
route.providerName ?? "",
|
|
2795
|
+
route.providerId ?? "",
|
|
2440
2796
|
routeProvider?.apiKey ?? "",
|
|
2441
2797
|
route.apiBase ?? "",
|
|
2442
2798
|
routeProvider?.wireApi ?? "",
|
|
@@ -4585,6 +4941,18 @@ var ServiceActionGrantStore = class {
|
|
|
4585
4941
|
if (Object.keys(callerGrants.actions).length === 0) delete data.grants[callerKey];
|
|
4586
4942
|
await this.save(data);
|
|
4587
4943
|
};
|
|
4944
|
+
revokeActionsByPrefix = async (actionIdPrefix) => {
|
|
4945
|
+
const data = await this.load();
|
|
4946
|
+
let changed = false;
|
|
4947
|
+
for (const [callerKey, callerGrants] of Object.entries(data.grants)) {
|
|
4948
|
+
for (const actionId of Object.keys(callerGrants.actions)) if (actionId.startsWith(actionIdPrefix)) {
|
|
4949
|
+
delete callerGrants.actions[actionId];
|
|
4950
|
+
changed = true;
|
|
4951
|
+
}
|
|
4952
|
+
if (Object.keys(callerGrants.actions).length === 0) delete data.grants[callerKey];
|
|
4953
|
+
}
|
|
4954
|
+
if (changed) await this.save(data);
|
|
4955
|
+
};
|
|
4588
4956
|
load = async () => {
|
|
4589
4957
|
try {
|
|
4590
4958
|
return normalizeStoreData(JSON.parse(await readFile(this.storePath, "utf8")));
|
|
@@ -4790,6 +5158,16 @@ var ServiceAppManager = class {
|
|
|
4790
5158
|
await this.runtimeService.restart(record.id);
|
|
4791
5159
|
return await this.getServiceApp(appId);
|
|
4792
5160
|
};
|
|
5161
|
+
deleteServiceApp = async (appId) => {
|
|
5162
|
+
const { record } = await this.requireServiceApp(appId);
|
|
5163
|
+
await this.runtimeService.restart(record.id);
|
|
5164
|
+
await rm(record.dirPath, { recursive: true });
|
|
5165
|
+
await this.createGrantStore().revokeActionsByPrefix(`${record.id}.`);
|
|
5166
|
+
return {
|
|
5167
|
+
deleted: true,
|
|
5168
|
+
id: record.id
|
|
5169
|
+
};
|
|
5170
|
+
};
|
|
4793
5171
|
dispose = async () => {
|
|
4794
5172
|
await this.runtimeService.dispose();
|
|
4795
5173
|
};
|
|
@@ -8153,6 +8531,7 @@ var NextclawKernel = class {
|
|
|
8153
8531
|
llmProviders = new LlmProviderManager();
|
|
8154
8532
|
llmUsage = new LlmUsageManager();
|
|
8155
8533
|
configManager;
|
|
8534
|
+
accessManager;
|
|
8156
8535
|
agents;
|
|
8157
8536
|
control;
|
|
8158
8537
|
skills;
|
|
@@ -8192,6 +8571,10 @@ var NextclawKernel = class {
|
|
|
8192
8571
|
channels: this.channels,
|
|
8193
8572
|
providerManager: this.llmProviders
|
|
8194
8573
|
});
|
|
8574
|
+
this.accessManager = new AccessManager({
|
|
8575
|
+
configManager: this.configManager,
|
|
8576
|
+
homeDir: options.homeDir
|
|
8577
|
+
});
|
|
8195
8578
|
this.sessionManager = new SessionManager({
|
|
8196
8579
|
configManager: this.configManager,
|
|
8197
8580
|
eventBus: this.eventBus,
|
|
@@ -8867,6 +9250,6 @@ function resolveLegacyEventType(message) {
|
|
|
8867
9250
|
return `message.${role || "other"}`;
|
|
8868
9251
|
}
|
|
8869
9252
|
//#endregion
|
|
8870
|
-
export { AgentManager, AgentRunClient, AgentRuntimeRegistry, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_TIMELINE_KIND, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionPreflightService, ContextWindowPreviewManager, DEFAULT_AGENT_RUNTIME_ENTRY_ID, ExtensionManager, GatewayInboundProcessor, LlmProviderManager, LlmUsageManager, LlmUsageStore, McpManager, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawKernel, PANEL_APP_AGENT_CAPABILITIES, PanelAppAssetTokenService, PanelAppError, PanelAppManager, ProviderManagerNcpLLMApi, ServiceAppError, ServiceAppManager, SessionManager, SessionRequestManager, SkillManager, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildSessionOrchestrationSection, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextWindowSignature, createLlmUsageRecord, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isContextCompactionTimelineMessage, isContextWindowSnapshot, isPanelAppAgentCapability, isPanelAppError, isReplyCapableChannel, isServiceAppError, listExtensionChannelIds, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, normalizeOptionalString, parseSkillFrontmatter, projectNcpMessagesWithContextCompaction, readContextWindowEventSessionId, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveNextclawNcpRunContext, resolveSessionChannelContext, runGatewayInboundLoop, sanitizeLlmUsage, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, stripSkillFrontmatter, syncSessionThinkingPreference, toNcpMessages, upsertContextCompactionTimelineMessage, waitForAgentRuntimeSessionReply };
|
|
9253
|
+
export { AccessManager, AgentManager, AgentRunClient, AgentRuntimeRegistry, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_TIMELINE_KIND, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionPreflightService, ContextWindowPreviewManager, DEFAULT_AGENT_RUNTIME_ENTRY_ID, ExtensionManager, GatewayInboundProcessor, LlmProviderManager, LlmUsageManager, LlmUsageStore, McpManager, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawKernel, PANEL_APP_AGENT_CAPABILITIES, PanelAppAssetTokenService, PanelAppError, PanelAppManager, ProviderManagerNcpLLMApi, ServiceAppError, ServiceAppManager, SessionManager, SessionRequestManager, SkillManager, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildSessionOrchestrationSection, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextWindowSignature, createLlmUsageRecord, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isContextCompactionTimelineMessage, isContextWindowSnapshot, isPanelAppAgentCapability, isPanelAppError, isReplyCapableChannel, isServiceAppError, listExtensionChannelIds, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, normalizeOptionalString, parseSkillFrontmatter, projectNcpMessagesWithContextCompaction, readContextWindowEventSessionId, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveNextclawNcpRunContext, resolveSessionChannelContext, runGatewayInboundLoop, sanitizeLlmUsage, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, stripSkillFrontmatter, syncSessionThinkingPreference, toNcpMessages, upsertContextCompactionTimelineMessage, waitForAgentRuntimeSessionReply };
|
|
8871
9254
|
|
|
8872
9255
|
//# sourceMappingURL=index.js.map
|