@botcord/daemon 0.2.89 → 0.2.90

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.
@@ -76,6 +76,15 @@ export async function startCloudDaemon(opts) {
76
76
  const credentialPathByAgentId = new Map();
77
77
  const hubUrlByAgentId = new Map();
78
78
  const displayNameByAgent = new Map();
79
+ const runtimeByAgentId = new Map();
80
+ const hermesProfileByAgentId = new Map();
81
+ const skillIndexOptionsForAgent = (agentId) => {
82
+ const hermesProfile = hermesProfileByAgentId.get(agentId);
83
+ return {
84
+ runtime: runtimeByAgentId.get(agentId) ?? opts.config.defaultRoute.adapter,
85
+ ...(hermesProfile ? { hermesProfile } : {}),
86
+ };
87
+ };
79
88
  // Seed each per-agent hub URL with the cloud-mode value so that even
80
89
  // before the first credential file is written the room-context fetcher
81
90
  // has somewhere sensible to point.
@@ -158,16 +167,16 @@ export async function startCloudDaemon(opts) {
158
167
  });
159
168
  };
160
169
  const installedAgentIds = new Set();
161
- const runtimeByAgentId = new Map();
162
170
  let controlChannel = null;
163
171
  const pushInstalledAgentSkillSnapshot = (agentId, reason) => {
164
172
  if (!controlChannel)
165
173
  return;
166
- const runtime = runtimeByAgentId.get(agentId) ?? opts.config.defaultRoute.adapter;
167
- const pushed = pushAgentSkillSnapshot(controlChannel, agentId, { runtime });
174
+ const skillIndexOptions = skillIndexOptionsForAgent(agentId);
175
+ const pushed = pushAgentSkillSnapshot(controlChannel, agentId, skillIndexOptions);
168
176
  logger.info("cloud control-channel: agent_skill_snapshot pushed", {
169
177
  agentId,
170
- runtime,
178
+ runtime: skillIndexOptions.runtime,
179
+ hermesProfile: skillIndexOptions.hermesProfile ?? null,
171
180
  reason,
172
181
  ok: pushed,
173
182
  });
@@ -176,6 +185,10 @@ export async function startCloudDaemon(opts) {
176
185
  installedAgentIds.add(info.agentId);
177
186
  if (info.runtime)
178
187
  runtimeByAgentId.set(info.agentId, info.runtime);
188
+ if (info.hermesProfile)
189
+ hermesProfileByAgentId.set(info.agentId, info.hermesProfile);
190
+ else if (info.runtime)
191
+ hermesProfileByAgentId.delete(info.agentId);
179
192
  credentialPathByAgentId.set(info.agentId, info.credentialsFile);
180
193
  if (info.hubUrl)
181
194
  hubUrlByAgentId.set(info.agentId, info.hubUrl);
@@ -186,6 +199,7 @@ export async function startCloudDaemon(opts) {
186
199
  agentId: info.agentId,
187
200
  activityTracker,
188
201
  roomContextBuilder,
202
+ skillIndexOptions: () => skillIndexOptionsForAgent(info.agentId),
189
203
  // Cloud daemons run isolated — no loop-risk guard wired in PR1;
190
204
  // the runtime adapter's wall-time budget enforces the equivalent.
191
205
  loopRiskBuilder: () => null,
package/dist/daemon.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { DaemonConfig } from "./config.js";
3
3
  import { type BootAgentsResult } from "./agent-discovery.js";
4
4
  import { ensureAgentWorkspace } from "./agent-workspace.js";
5
5
  import { UserAuthManager } from "./user-auth.js";
6
+ import { type SkillIndexOptions } from "./skill-index.js";
6
7
  import { classifyActivitySender } from "./sender-classify.js";
7
8
  export { classifyActivitySender };
8
9
  /** Minimal activity-tracker surface the inbound observer uses. */
@@ -65,9 +66,7 @@ export interface RuntimeSnapshotSink {
65
66
  * or wait for the next daemon restart). Exported for unit tests.
66
67
  */
67
68
  export declare function pushRuntimeSnapshot(sink: RuntimeSnapshotSink, liveSnapshot?: GatewayRuntimeSnapshot): boolean;
68
- export declare function pushAgentSkillSnapshot(sink: RuntimeSnapshotSink, agentId: string, opts?: {
69
- runtime?: string;
70
- }): boolean;
69
+ export declare function pushAgentSkillSnapshot(sink: RuntimeSnapshotSink, agentId: string, opts?: SkillIndexOptions): boolean;
71
70
  /** Options accepted by {@link startDaemon} — the P0.5 compatibility shim. */
72
71
  export interface DaemonRuntimeOptions {
73
72
  config: DaemonConfig;
package/dist/daemon.js CHANGED
@@ -221,6 +221,13 @@ export async function startDaemon(opts) {
221
221
  const agentIds = boot.agents.map((a) => a.agentId);
222
222
  const { credentialPathByAgentId, agentRuntimes } = backfillBootAgents(boot.agents, { logger });
223
223
  const gwConfig = toGatewayConfig(opts.config, { agentIds, agentRuntimes });
224
+ const skillIndexOptionsForAgent = (agentId) => {
225
+ const meta = agentRuntimes[agentId];
226
+ return {
227
+ runtime: meta?.runtime ?? opts.config.defaultRoute.adapter,
228
+ ...(meta?.hermesProfile ? { hermesProfile: meta.hermesProfile } : {}),
229
+ };
230
+ };
224
231
  // Per-agent hub URL — read from each credential file at boot. Used to
225
232
  // populate `BOTCORD_HUB` for runtime CLI subprocesses so the bundled
226
233
  // `botcord` CLI talks to the same hub the agent is registered against,
@@ -273,6 +280,7 @@ export async function startDaemon(opts) {
273
280
  activityTracker,
274
281
  roomContextBuilder,
275
282
  loopRiskBuilder,
283
+ skillIndexOptions: () => skillIndexOptionsForAgent(aid),
276
284
  }));
277
285
  }
278
286
  const buildSystemContext = (message) => {
@@ -372,11 +380,16 @@ export async function startDaemon(opts) {
372
380
  // next room-context fetch re-loads the BotCordClient against the new
373
381
  // credential file.
374
382
  credentialPathByAgentId.set(info.agentId, info.credentialsFile);
375
- if (info.runtime) {
376
- agentRuntimes[info.agentId] = {
383
+ if (info.runtime || info.hermesProfile) {
384
+ const next = {
377
385
  ...(agentRuntimes[info.agentId] ?? {}),
378
- runtime: info.runtime,
386
+ ...(info.runtime ? { runtime: info.runtime } : {}),
387
+ ...(info.hermesProfile ? { hermesProfile: info.hermesProfile } : {}),
379
388
  };
389
+ if (info.runtime && !info.hermesProfile) {
390
+ delete next.hermesProfile;
391
+ }
392
+ agentRuntimes[info.agentId] = next;
380
393
  }
381
394
  if (info.hubUrl)
382
395
  hubUrlByAgentId.set(info.agentId, info.hubUrl);
@@ -388,6 +401,7 @@ export async function startDaemon(opts) {
388
401
  activityTracker,
389
402
  roomContextBuilder,
390
403
  loopRiskBuilder,
404
+ skillIndexOptions: () => skillIndexOptionsForAgent(info.agentId),
391
405
  }));
392
406
  }
393
407
  };
@@ -507,11 +521,12 @@ export async function startDaemon(opts) {
507
521
  ok: pushed,
508
522
  });
509
523
  for (const agentId of agentIds) {
510
- const runtime = agentRuntimes[agentId]?.runtime ?? opts.config.defaultRoute.adapter;
511
- const skillsPushed = pushAgentSkillSnapshot(controlChannel, agentId, { runtime });
524
+ const skillIndexOptions = skillIndexOptionsForAgent(agentId);
525
+ const skillsPushed = pushAgentSkillSnapshot(controlChannel, agentId, skillIndexOptions);
512
526
  logger.info("control-channel: initial agent_skill_snapshot push", {
513
527
  agentId,
514
- runtime,
528
+ runtime: skillIndexOptions.runtime,
529
+ hermesProfile: skillIndexOptions.hermesProfile ?? null,
515
530
  ok: skillsPushed,
516
531
  });
517
532
  }
@@ -18,6 +18,7 @@ export interface InstalledAgentInfo {
18
18
  hubUrl: string;
19
19
  displayName?: string;
20
20
  runtime?: string;
21
+ hermesProfile?: string;
21
22
  }
22
23
  /**
23
24
  * Hook fired after `installLocalAgent` / `installExistingOpenclawBinding`
package/dist/provision.js CHANGED
@@ -20,12 +20,25 @@ import { discoverAgentCredentials } from "./agent-discovery.js";
20
20
  import { resolveMemoryDir } from "./working-memory.js";
21
21
  import { discoverRuntimeModelCatalog } from "./runtime-models.js";
22
22
  import { collectAgentSkillSnapshot } from "./skill-index.js";
23
+ import { installAgentSkillManifest, installBotLearnArchiveManifest, installVercelSkillsForAgent, } from "./skill-installer.js";
23
24
  import { buildRuntimeSelectionExtraArgs, mergeRuntimeExtraArgs, } from "./runtime-route-options.js";
24
25
  import { handleCloudGatewayRuntimeInbound, } from "./cloud-gateway-runtime.js";
25
- function runtimeForLoadedAgent(gateway, agentId) {
26
- return gateway.listManagedRoutes()
27
- .find((route) => route.match?.accountId === agentId)
28
- ?.runtime;
26
+ function skillIndexOptionsForLoadedAgent(gateway, agentId) {
27
+ const route = gateway.listManagedRoutes()
28
+ .find((entry) => entry.match?.accountId === agentId);
29
+ let credentials = null;
30
+ try {
31
+ credentials = loadStoredCredentials(defaultCredentialsFile(agentId));
32
+ }
33
+ catch {
34
+ credentials = null;
35
+ }
36
+ const runtime = route?.runtime ?? credentials?.runtime;
37
+ const hermesProfile = route?.hermesProfile ?? credentials?.hermesProfile;
38
+ return {
39
+ ...(runtime ? { runtime } : {}),
40
+ ...(hermesProfile ? { hermesProfile } : {}),
41
+ };
29
42
  }
30
43
  /**
31
44
  * Build a dispatcher function that routes a `ControlFrame` to the right
@@ -339,15 +352,76 @@ export function createProvisioner(opts) {
339
352
  },
340
353
  };
341
354
  }
342
- const runtime = runtimeForLoadedAgent(gateway, params.agentId);
343
- const result = collectAgentSkillSnapshot(params.agentId, { runtime });
355
+ const skillIndexOptions = skillIndexOptionsForLoadedAgent(gateway, params.agentId);
356
+ const result = collectAgentSkillSnapshot(params.agentId, skillIndexOptions);
344
357
  daemonLog.debug("list_agent_skills", {
345
358
  agentId: params.agentId,
346
- runtime,
359
+ runtime: skillIndexOptions.runtime,
360
+ hermesProfile: skillIndexOptions.hermesProfile ?? null,
347
361
  count: result.skills.length,
348
362
  });
349
363
  return { ok: true, result };
350
364
  }
365
+ case CONTROL_FRAME_TYPES.INSTALL_AGENT_SKILL: {
366
+ const params = (frame.params ?? {});
367
+ if (!params.agentId) {
368
+ return {
369
+ ok: false,
370
+ error: { code: "bad_params", message: "install_agent_skill requires params.agentId" },
371
+ };
372
+ }
373
+ const channels = gateway.snapshot().channels;
374
+ if (!channels[params.agentId]) {
375
+ return {
376
+ ok: false,
377
+ error: {
378
+ code: "agent_not_loaded",
379
+ message: `agent ${params.agentId} is not loaded in daemon gateway`,
380
+ },
381
+ };
382
+ }
383
+ const skillIndexOptions = skillIndexOptionsForLoadedAgent(gateway, params.agentId);
384
+ const runtime = skillIndexOptions.runtime;
385
+ const modes = [params.manifest, params.archiveManifest, params.vercel].filter(Boolean).length;
386
+ if (modes !== 1) {
387
+ return {
388
+ ok: false,
389
+ error: {
390
+ code: "bad_params",
391
+ message: "install_agent_skill requires exactly one of manifest, archiveManifest, or vercel",
392
+ },
393
+ };
394
+ }
395
+ let result;
396
+ try {
397
+ result = params.vercel
398
+ ? await installVercelSkillsForAgent({
399
+ agentId: params.agentId,
400
+ packageSpec: params.vercel.packageSpec,
401
+ skills: params.vercel.skills,
402
+ runtime,
403
+ })
404
+ : params.archiveManifest
405
+ ? installBotLearnArchiveManifest(params.agentId, params.archiveManifest, { runtime })
406
+ : installAgentSkillManifest(params.agentId, params.manifest, { runtime });
407
+ }
408
+ catch (err) {
409
+ return {
410
+ ok: false,
411
+ error: {
412
+ code: "skill_install_failed",
413
+ message: err instanceof Error ? err.message : String(err),
414
+ },
415
+ };
416
+ }
417
+ daemonLog.debug("install_agent_skill", {
418
+ agentId: params.agentId,
419
+ runtime,
420
+ installed: result.installed.map((s) => s.name),
421
+ snapshotCount: result.snapshot.skills.length,
422
+ });
423
+ return { ok: true, result };
424
+ }
351
425
  case "wake_agent": {
352
426
  return handleWakeAgent(gateway, frame.params);
353
427
  }
@@ -823,6 +897,7 @@ async function installLocalAgent(credentials, ctx) {
823
897
  hubUrl: credentials.hubUrl,
824
898
  ...(credentials.displayName ? { displayName: credentials.displayName } : {}),
825
899
  ...(credentials.runtime ? { runtime: credentials.runtime } : {}),
900
+ ...(credentials.hermesProfile ? { hermesProfile: credentials.hermesProfile } : {}),
826
901
  });
827
902
  }
828
903
  catch (err) {
@@ -904,6 +979,7 @@ async function installExistingOpenclawBinding(agentId, ctx) {
904
979
  hubUrl: credentials.hubUrl,
905
980
  ...(credentials.displayName ? { displayName: credentials.displayName } : {}),
906
981
  ...(credentials.runtime ? { runtime: credentials.runtime } : {}),
982
+ ...(credentials.hermesProfile ? { hermesProfile: credentials.hermesProfile } : {}),
907
983
  });
908
984
  }
909
985
  catch (err) {
@@ -54,6 +54,36 @@ const KIMI_FALLBACK_MODELS = [
54
54
  { id: "kimi-k2-5-preview", displayName: "kimi-k2-5-preview", provider: "kimi", source: "builtin" },
55
55
  { id: "kimi-k2-0711", displayName: "kimi-k2-0711", provider: "kimi", source: "builtin" },
56
56
  ];
57
+ // Gemini CLI has no `gemini models list` command — the model set is
58
+ // hard-coded inside the bundle's `VALID_GEMINI_MODELS` (see `@google/gemini-cli`
59
+ // bundle `chunk-BE42OOYM.js:275832`). We mirror the documented user-facing
60
+ // names here as a static catalog; the actual availability depends on the
61
+ // user's auth tier (gcloud ADC / Vertex / GEMINI_API_KEY) and project quota.
62
+ // The `auto` alias is the default — it lets gemini pick the best model.
63
+ const GEMINI_FALLBACK_MODELS = [
64
+ { id: "auto", displayName: "Auto (let Gemini pick)", provider: "google", source: "builtin", isDefault: true },
65
+ { id: "pro", displayName: "Pro alias", provider: "google", source: "builtin" },
66
+ { id: "flash", displayName: "Flash alias", provider: "google", source: "builtin" },
67
+ { id: "flash-lite", displayName: "Flash Lite alias", provider: "google", source: "builtin" },
68
+ { id: "gemini-2.5-pro", displayName: "Gemini 2.5 Pro", provider: "google", source: "builtin" },
69
+ { id: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash", provider: "google", source: "builtin" },
70
+ { id: "gemini-2.5-flash-lite", displayName: "Gemini 2.5 Flash Lite", provider: "google", source: "builtin" },
71
+ { id: "gemini-3-pro-preview", displayName: "Gemini 3 Pro (preview)", provider: "google", source: "builtin" },
72
+ { id: "gemini-3-flash-preview", displayName: "Gemini 3 Flash (preview)", provider: "google", source: "builtin" },
73
+ { id: "gemini-3.1-pro-preview", displayName: "Gemini 3.1 Pro (preview)", provider: "google", source: "builtin" },
74
+ {
75
+ id: "gemini-3.1-pro-preview-customtools",
76
+ displayName: "Gemini 3.1 Pro Custom Tools (preview)",
77
+ provider: "google",
78
+ source: "builtin",
79
+ },
80
+ {
81
+ id: "gemini-3.1-flash-lite-preview",
82
+ displayName: "Gemini 3.1 Flash Lite (preview)",
83
+ provider: "google",
84
+ source: "builtin",
85
+ },
86
+ ];
57
87
  const backgroundRefreshes = new Set();
58
88
  export function discoverRuntimeModelCatalog(entry) {
59
89
  if (!entry.result.available)
@@ -126,6 +156,22 @@ function runtimeCatalogStrategy(entry) {
126
156
  ],
127
157
  }),
128
158
  };
159
+ case "gemini":
160
+ // Gemini CLI exposes no runtime discovery command and its thinking
161
+ // budget / level (see bundle `ThinkingLevel`, `thinkingBudget`) is
162
+ // configured per-installation in `~/.gemini/settings.json`, not via
163
+ // any CLI flag — so we ship a static model catalog with no parameter
164
+ // controls. settings.json + BOTCORD_GEMINI_BIN feed the cache key so
165
+ // user-side reconfig (e.g. switching auth type) busts the cache.
166
+ return {
167
+ id: entry.id,
168
+ contextKey: runtimeCatalogContextKey(entry, {
169
+ settings: fileStatKey(path.join(homedir(), ".gemini", "settings.json")),
170
+ env: pickEnv(["BOTCORD_GEMINI_BIN", "GEMINI_CLI_HOME"]),
171
+ }),
172
+ discoverFresh: () => ({ models: GEMINI_FALLBACK_MODELS.slice() }),
173
+ fallback: () => ({ models: GEMINI_FALLBACK_MODELS.slice() }),
174
+ };
129
175
  default:
130
176
  return null;
131
177
  }
@@ -2,29 +2,41 @@ export interface SoftSkillEntry {
2
2
  name: string;
3
3
  path: string;
4
4
  source: string;
5
+ runtime?: string;
6
+ profile?: string;
5
7
  description?: string;
6
8
  mtimeMs: number;
7
9
  }
8
10
  export interface AgentSkillSnapshotEntry {
9
11
  name: string;
10
12
  source: string;
13
+ sourceDetail?: string;
14
+ runtime?: string;
15
+ path?: string;
16
+ profile?: string;
11
17
  description?: string;
12
18
  mtimeMs: number;
13
19
  }
14
20
  export interface AgentSkillSnapshot {
15
21
  agentId: string;
22
+ runtime?: string;
16
23
  skills: AgentSkillSnapshotEntry[];
17
24
  probedAt: number;
18
25
  }
19
26
  export interface SkillIndexOptions {
20
27
  extraDirs?: string[];
28
+ hermesProfile?: string;
21
29
  includeGlobal?: boolean;
22
30
  runtime?: string;
23
31
  }
24
- export declare function defaultSkillDirs(agentId: string, opts?: SkillIndexOptions): Array<{
32
+ interface SkillRoot {
25
33
  dir: string;
26
34
  source: string;
27
- }>;
35
+ runtime?: string;
36
+ profile?: string;
37
+ }
38
+ export declare function defaultSkillDirs(agentId: string, opts?: SkillIndexOptions): SkillRoot[];
28
39
  export declare function scanSoftSkills(agentId: string, opts?: SkillIndexOptions): SoftSkillEntry[];
29
40
  export declare function collectAgentSkillSnapshot(agentId: string, opts?: SkillIndexOptions): AgentSkillSnapshot;
30
41
  export declare function buildSoftSkillIndexPrompt(agentId: string, opts?: SkillIndexOptions): string | null;
42
+ export {};
@@ -1,7 +1,8 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync, } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { agentCodexHomeDir, agentWorkspaceDir, } from "./agent-workspace.js";
4
+ import { agentCodexHomeDir, agentHermesHomeDir, agentWorkspaceDir, } from "./agent-workspace.js";
5
+ import { hermesProfileHomeDir } from "./gateway/runtimes/hermes-agent.js";
5
6
  const MAX_SKILLS = 24;
6
7
  const MAX_DESCRIPTION_CHARS = 260;
7
8
  const MAX_SKILL_MD_READ_CHARS = 8192;
@@ -10,24 +11,50 @@ export function defaultSkillDirs(agentId, opts = {}) {
10
11
  const agentClaude = {
11
12
  dir: path.join(agentWorkspaceDir(agentId), ".claude", "skills"),
12
13
  source: "agent-claude",
14
+ runtime: "claude-code",
13
15
  };
14
16
  const agentCodex = {
15
17
  dir: path.join(agentCodexHomeDir(agentId), "skills"),
16
18
  source: "agent-codex",
19
+ runtime: "codex",
17
20
  };
18
- const dirs = runtimeFamily(opts.runtime) === "codex"
19
- ? [agentCodex, agentClaude]
20
- : [agentClaude, agentCodex];
21
- if (includeGlobal) {
22
- const globalClaude = { dir: path.join(homedir(), ".claude", "skills"), source: "global-claude" };
23
- const globalCodex = { dir: path.join(homedir(), ".codex", "skills"), source: "global-codex" };
24
- dirs.push(...(runtimeFamily(opts.runtime) === "codex"
25
- ? [globalCodex, globalClaude]
26
- : [globalClaude, globalCodex]));
21
+ const agentHermes = hermesSkillRoot(agentId, opts.hermesProfile);
22
+ const dirs = [];
23
+ switch (runtimeFamily(opts.runtime)) {
24
+ case "codex":
25
+ dirs.push(agentCodex);
26
+ if (includeGlobal) {
27
+ dirs.push({
28
+ dir: path.join(homedir(), ".codex", "skills"),
29
+ source: "global-codex",
30
+ runtime: "codex",
31
+ });
32
+ }
33
+ break;
34
+ case "hermes":
35
+ dirs.push(agentHermes);
36
+ break;
37
+ case "claude":
38
+ dirs.push(agentClaude);
39
+ if (includeGlobal) {
40
+ dirs.push({
41
+ dir: path.join(homedir(), ".claude", "skills"),
42
+ source: "global-claude",
43
+ runtime: "claude-code",
44
+ });
45
+ }
46
+ break;
47
+ case "other":
48
+ break;
27
49
  }
28
50
  const envDirs = parseSkillDirsEnv(process.env.BOTCORD_SKILL_DIRS);
29
51
  for (const dir of [...envDirs, ...(opts.extraDirs ?? [])]) {
30
- dirs.push({ dir, source: "external" });
52
+ dirs.push({
53
+ dir,
54
+ source: "external",
55
+ ...(opts.runtime ? { runtime: opts.runtime } : {}),
56
+ ...(opts.hermesProfile ? { profile: opts.hermesProfile } : {}),
57
+ });
31
58
  }
32
59
  return dedupeDirs(expandSkillRoots(dirs));
33
60
  }
@@ -65,6 +92,8 @@ export function scanSoftSkills(agentId, opts = {}) {
65
92
  name: parsed.name,
66
93
  path: skillMd,
67
94
  source: root.source,
95
+ ...(root.runtime ? { runtime: root.runtime } : {}),
96
+ ...(root.profile ? { profile: root.profile } : {}),
68
97
  description: parsed.description,
69
98
  mtimeMs: st.mtimeMs,
70
99
  };
@@ -79,9 +108,14 @@ export function scanSoftSkills(agentId, opts = {}) {
79
108
  export function collectAgentSkillSnapshot(agentId, opts = {}) {
80
109
  return {
81
110
  agentId,
111
+ ...(opts.runtime ? { runtime: opts.runtime } : {}),
82
112
  skills: scanSoftSkills(agentId, opts).map((skill) => ({
83
113
  name: skill.name,
84
- source: skill.source.startsWith("agent-") ? "workspace" : "runtime-global",
114
+ source: snapshotSource(skill.source),
115
+ sourceDetail: skill.source,
116
+ ...(skill.runtime ? { runtime: skill.runtime } : {}),
117
+ path: skill.path,
118
+ ...(skill.profile ? { profile: skill.profile } : {}),
85
119
  ...(skill.description ? { description: skill.description } : {}),
86
120
  mtimeMs: skill.mtimeMs,
87
121
  })),
@@ -157,7 +191,7 @@ function dedupeDirs(dirs) {
157
191
  if (seen.has(resolved))
158
192
  continue;
159
193
  seen.add(resolved);
160
- out.push({ dir: resolved, source: entry.source });
194
+ out.push({ ...entry, dir: resolved });
161
195
  }
162
196
  return out;
163
197
  }
@@ -166,46 +200,59 @@ function expandSkillRoots(dirs) {
166
200
  for (const entry of dirs) {
167
201
  out.push(entry);
168
202
  if (entry.source.includes("codex")) {
169
- out.push({ dir: path.join(entry.dir, ".system"), source: entry.source });
203
+ out.push({ ...entry, dir: path.join(entry.dir, ".system") });
170
204
  }
171
205
  }
172
206
  return out;
173
207
  }
208
+ function hermesSkillRoot(agentId, profile) {
209
+ if (profile) {
210
+ try {
211
+ return {
212
+ dir: path.join(hermesProfileHomeDir(profile), "skills"),
213
+ source: "agent-hermes-profile",
214
+ runtime: "hermes-agent",
215
+ profile,
216
+ };
217
+ }
218
+ catch {
219
+ // Corrupt legacy credentials should not make the whole skill snapshot fail.
220
+ }
221
+ }
222
+ return {
223
+ dir: path.join(agentHermesHomeDir(agentId), "skills"),
224
+ source: "agent-hermes",
225
+ runtime: "hermes-agent",
226
+ };
227
+ }
174
228
  function runtimeFamily(runtime) {
175
229
  if (runtime === "codex")
176
230
  return "codex";
231
+ if (runtime === "hermes-agent")
232
+ return "hermes";
233
+ if (!runtime)
234
+ return "claude";
177
235
  if (runtime === "claude-code")
178
236
  return "claude";
179
237
  return "other";
180
238
  }
181
- function priority(source, runtime) {
182
- if (runtimeFamily(runtime) === "codex") {
183
- switch (source) {
184
- case "agent-codex":
185
- return 0;
186
- case "global-codex":
187
- return 1;
188
- case "agent-claude":
189
- return 2;
190
- case "global-claude":
191
- return 3;
192
- default:
193
- return 4;
194
- }
195
- }
239
+ function priority(source, _runtime) {
196
240
  switch (source) {
197
241
  case "agent-claude":
198
- return 0;
199
242
  case "agent-codex":
200
- return 1;
243
+ case "agent-hermes":
244
+ case "agent-hermes-profile":
245
+ return 0;
201
246
  case "global-claude":
202
- return 2;
203
247
  case "global-codex":
204
- return 3;
248
+ return 1;
205
249
  default:
206
- return 4;
250
+ return 2;
207
251
  }
208
252
  }
253
+ function snapshotSource(source) {
254
+ return source.startsWith("agent-") ? "workspace" : "runtime-global";
255
+ }
209
256
  function unquote(value) {
210
257
  const trimmed = value.trim();
211
258
  if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
@@ -0,0 +1,61 @@
1
+ import { type ExecFileOptions } from "node:child_process";
2
+ import { type AgentSkillSnapshot } from "./skill-index.js";
3
+ export type SkillInstallTarget = "claude-code" | "codex";
4
+ export interface SkillFileManifest {
5
+ path: string;
6
+ content?: string;
7
+ sourcePath?: string;
8
+ }
9
+ export interface SkillManifestInput {
10
+ name?: string;
11
+ id?: string;
12
+ description?: string;
13
+ skillMd?: string;
14
+ markdown?: string;
15
+ files?: SkillFileManifest[];
16
+ targetRuntimes?: SkillInstallTarget[];
17
+ }
18
+ export interface SkillArchiveManifestInput {
19
+ name?: string;
20
+ id?: string;
21
+ description?: string;
22
+ skillMd?: string;
23
+ markdown?: string;
24
+ files?: SkillFileManifest[];
25
+ skills?: SkillManifestInput[];
26
+ targetRuntimes?: SkillInstallTarget[];
27
+ }
28
+ export interface NormalizedSkillManifest {
29
+ name: string;
30
+ description?: string;
31
+ skillMd: string;
32
+ files: SkillFileManifest[];
33
+ targetRuntimes?: SkillInstallTarget[];
34
+ }
35
+ export interface InstalledSkillRecord {
36
+ name: string;
37
+ targets: SkillInstallTarget[];
38
+ paths: string[];
39
+ }
40
+ export interface AgentSkillInstallResult {
41
+ agentId: string;
42
+ installed: InstalledSkillRecord[];
43
+ snapshot: AgentSkillSnapshot;
44
+ }
45
+ export interface InstallAgentSkillManifestOptions {
46
+ runtime?: string;
47
+ sourceRoot?: string;
48
+ }
49
+ export interface VercelSkillsInstallOptions {
50
+ agentId: string;
51
+ packageSpec: string;
52
+ skills?: string[];
53
+ runtime?: string;
54
+ executor?: VercelSkillsExecutor;
55
+ }
56
+ export type VercelSkillsExecutor = (command: string, args: string[], options: ExecFileOptions) => Promise<void>;
57
+ export declare function normalizeSkillManifest(input: SkillManifestInput): NormalizedSkillManifest;
58
+ export declare function installAgentSkillManifest(agentId: string, manifest: SkillManifestInput, opts?: InstallAgentSkillManifestOptions): AgentSkillInstallResult;
59
+ export declare function installBotLearnArchiveManifest(agentId: string, archive: SkillArchiveManifestInput, opts?: InstallAgentSkillManifestOptions): AgentSkillInstallResult;
60
+ export declare function installVercelSkillsForAgent(opts: VercelSkillsInstallOptions): Promise<AgentSkillInstallResult>;
61
+ export declare function buildVercelSkillsArgs(packageSpec: string, skills: string[] | undefined, targets: SkillInstallTarget[]): string[];