@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.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 { provider, name } = this.resolveProvider(effectiveModel);
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: effectiveModel,
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 = specs.map((spec) => ({
2345
- name: spec.name,
2346
- provider: providers[spec.name],
2347
- spec
2348
- })).filter((entry) => entry.provider?.enabled !== false && Boolean(entry.provider?.apiKey)).filter((entry) => entry.spec.keywords.some((keyword) => modelLower.includes(keyword)));
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 [name, provider] = customProvider;
2398
- return provider.enabled !== false ? {
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?.provider?.modelConfig
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