@babyclaw/gateway 0.0.0 → 0.2.0

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/main.js CHANGED
@@ -139,7 +139,8 @@ function looksLikeSecret({ value }) {
139
139
  }
140
140
 
141
141
  // src/runtime.ts
142
- import { resolve as resolve5 } from "path";
142
+ import { mkdirSync as mkdirSync3 } from "fs";
143
+ import { getBundledSkillsDir } from "@babyclaw/skills";
143
144
 
144
145
  // src/ai/agent.ts
145
146
  import {
@@ -1945,9 +1946,26 @@ import { z } from "zod";
1945
1946
  // src/utils/path.ts
1946
1947
  import { resolve, sep } from "path";
1947
1948
  import { stat } from "fs/promises";
1949
+ var BUNDLED_SKILLS_PREFIX = "bundled-skills/";
1948
1950
  function normalizeSeparators({ path }) {
1949
1951
  return path.replaceAll("\\", "/");
1950
1952
  }
1953
+ function resolveBundledSkillPath({
1954
+ bundledSkillsDir,
1955
+ requestedPath
1956
+ }) {
1957
+ const relative3 = requestedPath.slice(BUNDLED_SKILLS_PREFIX.length);
1958
+ const normalized = normalizeSeparators({ path: relative3 }).trim();
1959
+ if (normalized.length === 0) {
1960
+ throw new Error("Path is required");
1961
+ }
1962
+ const absoluteRoot = resolve(bundledSkillsDir);
1963
+ const absoluteTarget = resolve(absoluteRoot, normalized);
1964
+ if (!isSubPath({ parent: absoluteRoot, child: absoluteTarget })) {
1965
+ throw new Error("Path escapes bundled skills root");
1966
+ }
1967
+ return absoluteTarget;
1968
+ }
1951
1969
  function resolveWorkspacePath({
1952
1970
  workspaceRoot,
1953
1971
  requestedPath
@@ -3704,10 +3722,25 @@ function createWorkspaceTools({ context }) {
3704
3722
  defaultCode: "WORKSPACE_READ_FAILED",
3705
3723
  input: { path, format },
3706
3724
  action: async () => {
3707
- const absolutePath = resolveWorkspacePath({
3708
- workspaceRoot: context.workspaceRoot,
3709
- requestedPath: path
3710
- });
3725
+ const isBundled = path.startsWith(BUNDLED_SKILLS_PREFIX);
3726
+ let absolutePath;
3727
+ if (isBundled) {
3728
+ if (!context.bundledSkillsDir) {
3729
+ throw new ToolExecutionError({
3730
+ code: "BUNDLED_SKILLS_UNAVAILABLE",
3731
+ message: "Bundled skills directory is not configured"
3732
+ });
3733
+ }
3734
+ absolutePath = resolveBundledSkillPath({
3735
+ bundledSkillsDir: context.bundledSkillsDir,
3736
+ requestedPath: path
3737
+ });
3738
+ } else {
3739
+ absolutePath = resolveWorkspacePath({
3740
+ workspaceRoot: context.workspaceRoot,
3741
+ requestedPath: path
3742
+ });
3743
+ }
3711
3744
  const raw = await readFile6(absolutePath, "utf8");
3712
3745
  ensurePayloadWithinLimit({
3713
3746
  value: raw,
@@ -3765,6 +3798,12 @@ function createWorkspaceTools({ context }) {
3765
3798
  hasValue: value !== void 0
3766
3799
  },
3767
3800
  action: async () => {
3801
+ if (path.startsWith(BUNDLED_SKILLS_PREFIX)) {
3802
+ throw new ToolExecutionError({
3803
+ code: "BUNDLED_SKILLS_READONLY",
3804
+ message: "Bundled skills are read-only and cannot be written to"
3805
+ });
3806
+ }
3768
3807
  const absolutePath = resolveWorkspacePath({
3769
3808
  workspaceRoot: context.workspaceRoot,
3770
3809
  requestedPath: path
@@ -3904,6 +3943,12 @@ function createWorkspaceTools({ context }) {
3904
3943
  defaultCode: "WORKSPACE_DELETE_FAILED",
3905
3944
  input: { path, recursive },
3906
3945
  action: async () => {
3946
+ if (path.startsWith(BUNDLED_SKILLS_PREFIX)) {
3947
+ throw new ToolExecutionError({
3948
+ code: "BUNDLED_SKILLS_READONLY",
3949
+ message: "Bundled skills are read-only and cannot be deleted"
3950
+ });
3951
+ }
3907
3952
  const absolutePath = resolveWorkspacePath({
3908
3953
  workspaceRoot: context.workspaceRoot,
3909
3954
  requestedPath: path
@@ -3940,6 +3985,12 @@ function createWorkspaceTools({ context }) {
3940
3985
  defaultCode: "WORKSPACE_MOVE_FAILED",
3941
3986
  input: { from_path, to_path, overwrite },
3942
3987
  action: async () => {
3988
+ if (from_path.startsWith(BUNDLED_SKILLS_PREFIX) || to_path.startsWith(BUNDLED_SKILLS_PREFIX)) {
3989
+ throw new ToolExecutionError({
3990
+ code: "BUNDLED_SKILLS_READONLY",
3991
+ message: "Bundled skills are read-only and cannot be moved"
3992
+ });
3993
+ }
3943
3994
  const fromAbsolute = resolveWorkspacePath({
3944
3995
  workspaceRoot: context.workspaceRoot,
3945
3996
  requestedPath: from_path
@@ -4120,44 +4171,15 @@ function createUnifiedTools({
4120
4171
  };
4121
4172
  }
4122
4173
 
4123
- // src/onboarding/personality.ts
4124
- import { readFile as readFile7 } from "fs/promises";
4125
- import { join as join9 } from "path";
4126
- function getPersonalityFilePaths({ workspacePath }) {
4127
- return {
4128
- identityPath: join9(workspacePath, "IDENTITY.md"),
4129
- soulPath: join9(workspacePath, "SOUL.md"),
4130
- userPath: join9(workspacePath, "USER.md")
4131
- };
4132
- }
4133
- async function readPersonalityFiles({
4134
- workspacePath
4174
+ // src/bundled-skills/index.ts
4175
+ import { listBundledSlugs, readBundledSkillContent, getBundledSkillInfo } from "@babyclaw/skills";
4176
+
4177
+ // src/workspace/skills/types.ts
4178
+ function getSkillKey({
4179
+ frontmatter,
4180
+ slug
4135
4181
  }) {
4136
- const { identityPath, soulPath, userPath } = getPersonalityFilePaths({ workspacePath });
4137
- const [identity, soul, user] = await Promise.all([
4138
- readOptionalFile2({ path: identityPath }),
4139
- readOptionalFile2({ path: soulPath }),
4140
- readOptionalFile2({ path: userPath })
4141
- ]);
4142
- return {
4143
- identity,
4144
- soul,
4145
- user
4146
- };
4147
- }
4148
- function hasCompletePersonalityFiles(files) {
4149
- return typeof files.identity === "string" && files.identity.trim().length > 0 && typeof files.soul === "string" && files.soul.trim().length > 0 && typeof files.user === "string" && files.user.trim().length > 0;
4150
- }
4151
- async function readOptionalFile2({ path }) {
4152
- try {
4153
- return await readFile7(path, "utf8");
4154
- } catch (error) {
4155
- const maybeErrno = error;
4156
- if (maybeErrno.code === "ENOENT") {
4157
- return null;
4158
- }
4159
- throw error;
4160
- }
4182
+ return frontmatter?.openclaw?.skillKey ?? frontmatter?.name ?? slug;
4161
4183
  }
4162
4184
 
4163
4185
  // src/workspace/skills/eligibility.ts
@@ -4167,54 +4189,84 @@ function getEligibleSkills({
4167
4189
  skillsConfig,
4168
4190
  fullConfig
4169
4191
  }) {
4170
- return skills.filter((skill) => shouldIncludeSkill({ skill, skillsConfig, fullConfig }));
4192
+ return skills.filter((skill) => {
4193
+ const { frontmatter, slug } = skill;
4194
+ const entry = skillsConfig.entries[getSkillKey({ frontmatter, slug })];
4195
+ if (entry?.enabled === false) return false;
4196
+ return checkSkillEligibility({ frontmatter, skillsConfig, fullConfig }).eligible;
4197
+ });
4171
4198
  }
4172
- function shouldIncludeSkill({
4173
- skill,
4199
+ function checkSkillEligibility({
4200
+ frontmatter,
4174
4201
  skillsConfig,
4175
4202
  fullConfig
4176
4203
  }) {
4177
- const { frontmatter } = skill;
4204
+ if (frontmatter.disableModelInvocation) {
4205
+ return { eligible: false, reason: "Model invocation disabled" };
4206
+ }
4178
4207
  const openclaw = frontmatter.openclaw;
4179
- const skillKey = openclaw?.skillKey ?? frontmatter.name;
4180
- const entry = skillsConfig.entries[skillKey];
4181
- if (entry?.enabled === false) return false;
4182
- if (frontmatter.disableModelInvocation) return false;
4183
4208
  if (openclaw?.os && openclaw.os.length > 0) {
4184
- if (!openclaw.os.includes(process.platform)) return false;
4209
+ if (!openclaw.os.includes(process.platform)) {
4210
+ return {
4211
+ eligible: false,
4212
+ reason: `Requires OS: ${openclaw.os.join(", ")} (current: ${process.platform})`
4213
+ };
4214
+ }
4215
+ }
4216
+ if (openclaw?.always) {
4217
+ return { eligible: true, reason: null };
4185
4218
  }
4186
- if (openclaw?.always) return true;
4187
4219
  const requires = openclaw?.requires;
4188
- if (!requires) return true;
4220
+ if (!requires) {
4221
+ return { eligible: true, reason: null };
4222
+ }
4189
4223
  if (requires.bins && requires.bins.length > 0) {
4190
- if (!requires.bins.every((bin) => binaryExists({ name: bin }))) return false;
4224
+ const missing = requires.bins.filter((bin) => !binaryExists({ name: bin }));
4225
+ if (missing.length > 0) {
4226
+ return { eligible: false, reason: `Missing binaries: ${missing.join(", ")}` };
4227
+ }
4191
4228
  }
4192
4229
  if (requires.anyBins && requires.anyBins.length > 0) {
4193
- if (!requires.anyBins.some((bin) => binaryExists({ name: bin }))) return false;
4230
+ if (!requires.anyBins.some((bin) => binaryExists({ name: bin }))) {
4231
+ return {
4232
+ eligible: false,
4233
+ reason: `Requires at least one of: ${requires.anyBins.join(", ")}`
4234
+ };
4235
+ }
4194
4236
  }
4195
4237
  if (requires.env && requires.env.length > 0) {
4238
+ const skillKey = openclaw?.skillKey ?? frontmatter.name;
4239
+ const entry = skillsConfig.entries[skillKey];
4196
4240
  const primaryEnv = openclaw?.primaryEnv;
4197
4241
  const hasApiKeyInConfig = Boolean(entry?.apiKey);
4198
4242
  for (const envVar of requires.env) {
4199
4243
  if (process.env[envVar]) continue;
4200
4244
  if (primaryEnv === envVar && hasApiKeyInConfig) continue;
4201
- return false;
4245
+ return { eligible: false, reason: `Missing environment variable: ${envVar}` };
4202
4246
  }
4203
4247
  }
4204
4248
  if (requires.config && requires.config.length > 0) {
4205
4249
  for (const configPath of requires.config) {
4206
- if (!getConfigValue({ config: fullConfig, path: configPath })) return false;
4250
+ if (!getConfigValue({ config: fullConfig, path: configPath })) {
4251
+ return { eligible: false, reason: `Missing config value: ${configPath}` };
4252
+ }
4207
4253
  }
4208
4254
  }
4209
- return true;
4255
+ return { eligible: true, reason: null };
4210
4256
  }
4257
+ var binaryExistsCache = /* @__PURE__ */ new Map();
4211
4258
  function binaryExists({ name }) {
4259
+ const cached = binaryExistsCache.get(name);
4260
+ if (cached !== void 0) return cached;
4261
+ let exists;
4212
4262
  try {
4213
4263
  execFileSync("which", [name], { stdio: "ignore" });
4214
- return true;
4264
+ exists = true;
4215
4265
  } catch {
4216
- return false;
4266
+ exists = false;
4217
4267
  }
4268
+ binaryExistsCache.set(name, exists);
4269
+ return exists;
4218
4270
  }
4219
4271
  function getConfigValue({
4220
4272
  config,
@@ -4229,6 +4281,90 @@ function getConfigValue({
4229
4281
  return current;
4230
4282
  }
4231
4283
 
4284
+ // src/bundled-skills/index.ts
4285
+ function listBundledSkills({
4286
+ skillsConfig,
4287
+ fullConfig
4288
+ }) {
4289
+ const slugs = listBundledSlugs();
4290
+ return slugs.map((slug) => {
4291
+ const content = readBundledSkillContent({ slug });
4292
+ if (!content) {
4293
+ return {
4294
+ slug,
4295
+ frontmatter: null,
4296
+ enabled: false,
4297
+ eligible: false,
4298
+ ineligibilityReason: "Could not read SKILL.md"
4299
+ };
4300
+ }
4301
+ const raw = parseFrontmatter({ content });
4302
+ const frontmatter = raw ? buildFrontmatter({ raw }) : null;
4303
+ const skillKey = getSkillKey({ frontmatter, slug });
4304
+ const entry = skillsConfig.entries[skillKey];
4305
+ const enabled = entry?.enabled === true;
4306
+ const { eligible, reason } = frontmatter ? checkSkillEligibility({ frontmatter, skillsConfig, fullConfig }) : { eligible: false, reason: "Invalid frontmatter" };
4307
+ return {
4308
+ slug,
4309
+ frontmatter,
4310
+ enabled,
4311
+ eligible,
4312
+ ineligibilityReason: reason
4313
+ };
4314
+ });
4315
+ }
4316
+ function getEnabledBundledSkills({
4317
+ skillsConfig,
4318
+ fullConfig
4319
+ }) {
4320
+ const all = listBundledSkills({ skillsConfig, fullConfig });
4321
+ return all.filter((s) => s.enabled && s.eligible && s.frontmatter).map((s) => ({
4322
+ frontmatter: s.frontmatter,
4323
+ slug: s.slug,
4324
+ relativePath: `bundled-skills/${s.slug}/SKILL.md`
4325
+ }));
4326
+ }
4327
+
4328
+ // src/onboarding/personality.ts
4329
+ import { readFile as readFile7 } from "fs/promises";
4330
+ import { join as join9 } from "path";
4331
+ function getPersonalityFilePaths({ workspacePath }) {
4332
+ return {
4333
+ identityPath: join9(workspacePath, "IDENTITY.md"),
4334
+ soulPath: join9(workspacePath, "SOUL.md"),
4335
+ userPath: join9(workspacePath, "USER.md")
4336
+ };
4337
+ }
4338
+ async function readPersonalityFiles({
4339
+ workspacePath
4340
+ }) {
4341
+ const { identityPath, soulPath, userPath } = getPersonalityFilePaths({ workspacePath });
4342
+ const [identity, soul, user] = await Promise.all([
4343
+ readOptionalFile2({ path: identityPath }),
4344
+ readOptionalFile2({ path: soulPath }),
4345
+ readOptionalFile2({ path: userPath })
4346
+ ]);
4347
+ return {
4348
+ identity,
4349
+ soul,
4350
+ user
4351
+ };
4352
+ }
4353
+ function hasCompletePersonalityFiles(files) {
4354
+ return typeof files.identity === "string" && files.identity.trim().length > 0 && typeof files.soul === "string" && files.soul.trim().length > 0 && typeof files.user === "string" && files.user.trim().length > 0;
4355
+ }
4356
+ async function readOptionalFile2({ path }) {
4357
+ try {
4358
+ return await readFile7(path, "utf8");
4359
+ } catch (error) {
4360
+ const maybeErrno = error;
4361
+ if (maybeErrno.code === "ENOENT") {
4362
+ return null;
4363
+ }
4364
+ throw error;
4365
+ }
4366
+ }
4367
+
4232
4368
  // src/agent/context.ts
4233
4369
  async function loadAgentContext({
4234
4370
  workspacePath,
@@ -4241,7 +4377,11 @@ async function loadAgentContext({
4241
4377
  readWorkspaceGuide({ workspacePath }),
4242
4378
  scanWorkspaceSkills({ workspacePath })
4243
4379
  ]);
4244
- const skills = getEligibleSkills({ skills: allSkills, skillsConfig, fullConfig });
4380
+ const workspaceSkills = getEligibleSkills({ skills: allSkills, skillsConfig, fullConfig });
4381
+ const bundledSkills = getEnabledBundledSkills({ skillsConfig, fullConfig });
4382
+ const workspaceSlugs = new Set(workspaceSkills.map((s) => s.slug));
4383
+ const dedupedBundled = bundledSkills.filter((s) => !workspaceSlugs.has(s.slug));
4384
+ const skills = [...workspaceSkills, ...dedupedBundled];
4245
4385
  const personalityFiles = hasCompletePersonalityFiles(rawPersonalityFiles) ? rawPersonalityFiles : void 0;
4246
4386
  return { personalityFiles, toolNotesContent, agentsContent, skills };
4247
4387
  }
@@ -4422,6 +4562,7 @@ var AgentTurnOrchestrator = class {
4422
4562
  if (abortSignal.aborted) return;
4423
4563
  const {
4424
4564
  workspacePath,
4565
+ bundledSkillsDir,
4425
4566
  aiAgent,
4426
4567
  sessionManager,
4427
4568
  schedulerService,
@@ -4521,6 +4662,7 @@ ${description}`;
4521
4662
  toolDeps: this.toolDeps,
4522
4663
  executionContext: {
4523
4664
  workspaceRoot: workspacePath,
4665
+ bundledSkillsDir,
4524
4666
  botTimezone: schedulerService.getTimezone(),
4525
4667
  platform: event.platform,
4526
4668
  chatId: event.chatId,
@@ -5835,11 +5977,11 @@ var ChatRegistry = class {
5835
5977
 
5836
5978
  // src/config/loader.ts
5837
5979
  import { access, mkdir as mkdir5, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
5838
- import { dirname as dirname5 } from "path";
5980
+ import { dirname as dirname6 } from "path";
5839
5981
 
5840
5982
  // src/config/paths.ts
5841
5983
  import { homedir as homedir2 } from "os";
5842
- import { join as join11 } from "path";
5984
+ import { dirname as dirname5, join as join11, resolve as resolve4, isAbsolute } from "path";
5843
5985
  var CONFIG_PATH_ENV_VAR = "BABYCLAW_CONFIG_PATH";
5844
5986
  function getDefaultConfigPath() {
5845
5987
  return join11(homedir2(), ".babyclaw", "babyclaw.json");
@@ -5851,6 +5993,18 @@ function getConfigPath() {
5851
5993
  }
5852
5994
  return getDefaultConfigPath();
5853
5995
  }
5996
+ function getConfigDir() {
5997
+ return dirname5(getConfigPath());
5998
+ }
5999
+ function resolveWorkspaceRoot({ configRoot }) {
6000
+ if (configRoot === "~" || configRoot.startsWith("~/")) {
6001
+ return join11(homedir2(), configRoot.slice(2));
6002
+ }
6003
+ if (isAbsolute(configRoot)) {
6004
+ return configRoot;
6005
+ }
6006
+ return resolve4(getConfigDir(), configRoot);
6007
+ }
5854
6008
 
5855
6009
  // src/config/schema.ts
5856
6010
  import { z as z11 } from "zod";
@@ -6004,9 +6158,9 @@ var babyclawConfigSchema = z11.object({
6004
6158
  timezone: "UTC"
6005
6159
  }),
6006
6160
  workspace: z11.object({
6007
- root: z11.string().min(1).default(".")
6161
+ root: z11.string().min(1).default("~/babyclaw")
6008
6162
  }).strict().default({
6009
- root: "."
6163
+ root: "~/babyclaw"
6010
6164
  }),
6011
6165
  session: z11.object({
6012
6166
  maxMessagesPerSession: z11.number().int().positive().default(120),
@@ -6066,7 +6220,7 @@ var DEFAULT_CONFIG_TEMPLATE = {
6066
6220
  timezone: "UTC"
6067
6221
  },
6068
6222
  workspace: {
6069
- root: "."
6223
+ root: "~/babyclaw"
6070
6224
  },
6071
6225
  session: {
6072
6226
  maxMessagesPerSession: 120,
@@ -6123,6 +6277,22 @@ ${issues}`);
6123
6277
  log.info({ configPath }, "Configuration validated successfully");
6124
6278
  return parsed.data;
6125
6279
  }
6280
+ async function loadConfigRaw() {
6281
+ const configPath = getConfigPath();
6282
+ try {
6283
+ await access(configPath);
6284
+ } catch {
6285
+ return null;
6286
+ }
6287
+ const raw = await readFile8(configPath, "utf8");
6288
+ const json = parseJsonConfig({ raw, configPath });
6289
+ normalizeLegacyTelegramConfig({ json });
6290
+ const parsed = babyclawConfigSchema.safeParse(json);
6291
+ if (!parsed.success) {
6292
+ return null;
6293
+ }
6294
+ return parsed.data;
6295
+ }
6126
6296
  async function ensureConfigFileExists({ configPath }) {
6127
6297
  try {
6128
6298
  await access(configPath);
@@ -6131,7 +6301,7 @@ async function ensureConfigFileExists({ configPath }) {
6131
6301
  if (error.code !== "ENOENT") {
6132
6302
  throw error;
6133
6303
  }
6134
- await mkdir5(dirname5(configPath), { recursive: true });
6304
+ await mkdir5(dirname6(configPath), { recursive: true });
6135
6305
  await writeFile5(configPath, getDefaultConfigTemplate(), "utf8");
6136
6306
  getLogger().warn({ configPath }, "Created config file. Fill required secrets and restart.");
6137
6307
  }
@@ -6303,6 +6473,7 @@ var HeartbeatExecutor = class {
6303
6473
  toolDeps: this.toolDeps,
6304
6474
  executionContext: {
6305
6475
  workspaceRoot: workspacePath,
6476
+ bundledSkillsDir: this.toolDeps.bundledSkillsDir,
6306
6477
  botTimezone: schedulerService.getTimezone(),
6307
6478
  platform: mainChat.platform,
6308
6479
  chatId: platformChatId,
@@ -6759,6 +6930,7 @@ var SchedulerExecutor = class {
6759
6930
  toolDeps: this.toolDeps,
6760
6931
  executionContext: {
6761
6932
  workspaceRoot: workspacePath,
6933
+ bundledSkillsDir: this.toolDeps.bundledSkillsDir,
6762
6934
  botTimezone: schedulerService.getTimezone(),
6763
6935
  platform: this.channelSender.platform,
6764
6936
  chatId: chatIdStr,
@@ -7505,13 +7677,13 @@ function createDatabase({ workspacePath }) {
7505
7677
  }
7506
7678
 
7507
7679
  // src/database/migrate.ts
7508
- import { dirname as dirname6, resolve as resolve4 } from "path";
7680
+ import { dirname as dirname7, resolve as resolve5 } from "path";
7509
7681
  import { fileURLToPath } from "url";
7510
7682
  import { migrate } from "drizzle-orm/better-sqlite3/migrator";
7511
- var __dirname = dirname6(fileURLToPath(import.meta.url));
7683
+ var __dirname = dirname7(fileURLToPath(import.meta.url));
7512
7684
  function applyMigrations({ workspacePath }) {
7513
7685
  const db = createDatabase({ workspacePath });
7514
- const migrationsFolder = resolve4(__dirname, "..", "drizzle");
7686
+ const migrationsFolder = resolve5(__dirname, "..", "drizzle");
7515
7687
  migrate(db, { migrationsFolder });
7516
7688
  }
7517
7689
 
@@ -7549,7 +7721,8 @@ var GatewayRuntime = class {
7549
7721
  },
7550
7722
  "Gateway configuration summary"
7551
7723
  );
7552
- const workspacePath = resolve5(process.cwd(), config.workspace.root);
7724
+ const workspacePath = resolveWorkspaceRoot({ configRoot: config.workspace.root });
7725
+ mkdirSync3(workspacePath, { recursive: true });
7553
7726
  log.info("Applying database migrations...");
7554
7727
  applyMigrations({ workspacePath });
7555
7728
  log.info("Database migrations applied");
@@ -7633,6 +7806,7 @@ var GatewayRuntime = class {
7633
7806
  const adminSocketPath = getAdminSocketPath();
7634
7807
  const toolDeps = {
7635
7808
  workspacePath,
7809
+ bundledSkillsDir: getBundledSkillsDir(),
7636
7810
  aiAgent,
7637
7811
  sessionManager,
7638
7812
  schedulerService,
@@ -7738,6 +7912,16 @@ var GatewayRuntime = class {
7738
7912
  void this.stop().then(() => process.exit(0));
7739
7913
  });
7740
7914
  return { ok: true };
7915
+ },
7916
+ "/reload-skills": async () => {
7917
+ const freshConfig = await loadConfigRaw();
7918
+ if (!freshConfig) {
7919
+ return { ok: false, error: "config_not_found" };
7920
+ }
7921
+ toolDeps.skillsConfig = freshConfig.skills;
7922
+ toolDeps.fullConfig = freshConfig;
7923
+ log.info("Skills configuration reloaded via admin socket");
7924
+ return { ok: true };
7741
7925
  }
7742
7926
  }
7743
7927
  });