@botcord/daemon 0.2.88 → 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.
- package/dist/cloud-daemon.js +18 -4
- package/dist/daemon.d.ts +2 -3
- package/dist/daemon.js +21 -6
- package/dist/gateway/runtimes/gemini.d.ts +57 -5
- package/dist/gateway/runtimes/gemini.js +266 -5
- package/dist/gateway/runtimes/registry.d.ts +1 -1
- package/dist/gateway/runtimes/registry.js +3 -2
- package/dist/provision.d.ts +1 -0
- package/dist/provision.js +83 -7
- package/dist/runtime-models.js +46 -0
- package/dist/skill-index.d.ts +14 -2
- package/dist/skill-index.js +81 -34
- package/dist/skill-installer.d.ts +61 -0
- package/dist/skill-installer.js +340 -0
- package/dist/system-context.d.ts +6 -0
- package/dist/system-context.js +1 -1
- package/package.json +3 -3
- package/src/__tests__/runtime-discovery.test.ts +6 -3
- package/src/__tests__/runtime-models.test.ts +53 -0
- package/src/__tests__/skill-index.test.ts +89 -13
- package/src/__tests__/skill-installer.test.ts +224 -0
- package/src/cloud-daemon.ts +17 -4
- package/src/daemon.ts +23 -8
- package/src/gateway/__tests__/gemini-adapter.test.ts +357 -0
- package/src/gateway/runtimes/gemini.ts +301 -7
- package/src/gateway/runtimes/registry.ts +4 -2
- package/src/provision.ts +90 -12
- package/src/runtime-models.ts +47 -0
- package/src/skill-index.ts +103 -47
- package/src/skill-installer.ts +472 -0
- package/src/system-context.ts +7 -1
|
@@ -91,14 +91,16 @@ export const hermesAgentModule: RuntimeModule = {
|
|
|
91
91
|
'Install: pip install "hermes-agent[acp]" (or set BOTCORD_HERMES_AGENT_BIN to the absolute path of hermes-acp)',
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
/** Built-in runtime module entry for Gemini
|
|
94
|
+
/** Built-in runtime module entry for Gemini CLI. */
|
|
95
95
|
export const geminiModule: RuntimeModule = {
|
|
96
96
|
id: "gemini",
|
|
97
97
|
displayName: "Gemini CLI",
|
|
98
98
|
binary: "gemini",
|
|
99
|
+
envVar: "BOTCORD_GEMINI_BIN",
|
|
99
100
|
probe: () => probeGemini(),
|
|
100
101
|
create: () => new GeminiAdapter(),
|
|
101
|
-
|
|
102
|
+
installHint:
|
|
103
|
+
"Install with `npm install -g @google/gemini-cli` (or `brew install gemini-cli`) and run `gemini` once to complete authentication. Override the binary with BOTCORD_GEMINI_BIN.",
|
|
102
104
|
};
|
|
103
105
|
|
|
104
106
|
/** Built-in runtime module entry for OpenClaw (ACP). */
|
package/src/provision.ts
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
type StoredBotCordCredentials,
|
|
29
29
|
type UpdateAgentParams,
|
|
30
30
|
type GatewayInboundFrame,
|
|
31
|
+
type InstallAgentSkillParams,
|
|
32
|
+
type ListAgentSkillsParams,
|
|
31
33
|
} from "@botcord/protocol-core";
|
|
32
34
|
import type { Gateway } from "./gateway/index.js";
|
|
33
35
|
import type { GatewayInboundMessage } from "./gateway/index.js";
|
|
@@ -75,7 +77,12 @@ import { log as daemonLog } from "./log.js";
|
|
|
75
77
|
import { discoverAgentCredentials } from "./agent-discovery.js";
|
|
76
78
|
import { resolveMemoryDir } from "./working-memory.js";
|
|
77
79
|
import { discoverRuntimeModelCatalog } from "./runtime-models.js";
|
|
78
|
-
import { collectAgentSkillSnapshot } from "./skill-index.js";
|
|
80
|
+
import { collectAgentSkillSnapshot, type SkillIndexOptions } from "./skill-index.js";
|
|
81
|
+
import {
|
|
82
|
+
installAgentSkillManifest,
|
|
83
|
+
installBotLearnArchiveManifest,
|
|
84
|
+
installVercelSkillsForAgent,
|
|
85
|
+
} from "./skill-installer.js";
|
|
79
86
|
import {
|
|
80
87
|
buildRuntimeSelectionExtraArgs,
|
|
81
88
|
mergeRuntimeExtraArgs,
|
|
@@ -85,14 +92,21 @@ import {
|
|
|
85
92
|
type CloudGatewayTypingEmitter,
|
|
86
93
|
} from "./cloud-gateway-runtime.js";
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
function skillIndexOptionsForLoadedAgent(gateway: Gateway, agentId: string): SkillIndexOptions {
|
|
96
|
+
const route = gateway.listManagedRoutes()
|
|
97
|
+
.find((entry) => entry.match?.accountId === agentId);
|
|
98
|
+
let credentials: StoredBotCordCredentials | null = null;
|
|
99
|
+
try {
|
|
100
|
+
credentials = loadStoredCredentials(defaultCredentialsFile(agentId));
|
|
101
|
+
} catch {
|
|
102
|
+
credentials = null;
|
|
103
|
+
}
|
|
104
|
+
const runtime = route?.runtime ?? credentials?.runtime;
|
|
105
|
+
const hermesProfile = route?.hermesProfile ?? credentials?.hermesProfile;
|
|
106
|
+
return {
|
|
107
|
+
...(runtime ? { runtime } : {}),
|
|
108
|
+
...(hermesProfile ? { hermesProfile } : {}),
|
|
109
|
+
};
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
/**
|
|
@@ -108,6 +122,7 @@ export interface InstalledAgentInfo {
|
|
|
108
122
|
hubUrl: string;
|
|
109
123
|
displayName?: string;
|
|
110
124
|
runtime?: string;
|
|
125
|
+
hermesProfile?: string;
|
|
111
126
|
}
|
|
112
127
|
|
|
113
128
|
/**
|
|
@@ -516,16 +531,77 @@ export function createProvisioner(opts: ProvisionerOptions): (
|
|
|
516
531
|
},
|
|
517
532
|
};
|
|
518
533
|
}
|
|
519
|
-
const
|
|
520
|
-
const result = collectAgentSkillSnapshot(params.agentId,
|
|
534
|
+
const skillIndexOptions = skillIndexOptionsForLoadedAgent(gateway, params.agentId);
|
|
535
|
+
const result = collectAgentSkillSnapshot(params.agentId, skillIndexOptions);
|
|
521
536
|
daemonLog.debug("list_agent_skills", {
|
|
522
537
|
agentId: params.agentId,
|
|
523
|
-
runtime,
|
|
538
|
+
runtime: skillIndexOptions.runtime,
|
|
539
|
+
hermesProfile: skillIndexOptions.hermesProfile ?? null,
|
|
524
540
|
count: result.skills.length,
|
|
525
541
|
});
|
|
526
542
|
return { ok: true, result };
|
|
527
543
|
}
|
|
528
544
|
|
|
545
|
+
case CONTROL_FRAME_TYPES.INSTALL_AGENT_SKILL: {
|
|
546
|
+
const params = (frame.params ?? {}) as unknown as InstallAgentSkillParams;
|
|
547
|
+
if (!params.agentId) {
|
|
548
|
+
return {
|
|
549
|
+
ok: false,
|
|
550
|
+
error: { code: "bad_params", message: "install_agent_skill requires params.agentId" },
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const channels = gateway.snapshot().channels;
|
|
554
|
+
if (!channels[params.agentId]) {
|
|
555
|
+
return {
|
|
556
|
+
ok: false,
|
|
557
|
+
error: {
|
|
558
|
+
code: "agent_not_loaded",
|
|
559
|
+
message: `agent ${params.agentId} is not loaded in daemon gateway`,
|
|
560
|
+
},
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
const skillIndexOptions = skillIndexOptionsForLoadedAgent(gateway, params.agentId);
|
|
564
|
+
const runtime = skillIndexOptions.runtime;
|
|
565
|
+
const modes = [params.manifest, params.archiveManifest, params.vercel].filter(Boolean).length;
|
|
566
|
+
if (modes !== 1) {
|
|
567
|
+
return {
|
|
568
|
+
ok: false,
|
|
569
|
+
error: {
|
|
570
|
+
code: "bad_params",
|
|
571
|
+
message: "install_agent_skill requires exactly one of manifest, archiveManifest, or vercel",
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
let result;
|
|
576
|
+
try {
|
|
577
|
+
result = params.vercel
|
|
578
|
+
? await installVercelSkillsForAgent({
|
|
579
|
+
agentId: params.agentId,
|
|
580
|
+
packageSpec: params.vercel.packageSpec,
|
|
581
|
+
skills: params.vercel.skills,
|
|
582
|
+
runtime,
|
|
583
|
+
})
|
|
584
|
+
: params.archiveManifest
|
|
585
|
+
? installBotLearnArchiveManifest(params.agentId, params.archiveManifest, { runtime })
|
|
586
|
+
: installAgentSkillManifest(params.agentId, params.manifest!, { runtime });
|
|
587
|
+
} catch (err) {
|
|
588
|
+
return {
|
|
589
|
+
ok: false,
|
|
590
|
+
error: {
|
|
591
|
+
code: "skill_install_failed",
|
|
592
|
+
message: err instanceof Error ? err.message : String(err),
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
daemonLog.debug("install_agent_skill", {
|
|
597
|
+
agentId: params.agentId,
|
|
598
|
+
runtime,
|
|
599
|
+
installed: result.installed.map((s) => s.name),
|
|
600
|
+
snapshotCount: result.snapshot.skills.length,
|
|
601
|
+
});
|
|
602
|
+
return { ok: true, result };
|
|
603
|
+
}
|
|
604
|
+
|
|
529
605
|
case "wake_agent": {
|
|
530
606
|
return handleWakeAgent(gateway, frame.params);
|
|
531
607
|
}
|
|
@@ -1118,6 +1194,7 @@ async function installLocalAgent(
|
|
|
1118
1194
|
hubUrl: credentials.hubUrl,
|
|
1119
1195
|
...(credentials.displayName ? { displayName: credentials.displayName } : {}),
|
|
1120
1196
|
...(credentials.runtime ? { runtime: credentials.runtime } : {}),
|
|
1197
|
+
...(credentials.hermesProfile ? { hermesProfile: credentials.hermesProfile } : {}),
|
|
1121
1198
|
});
|
|
1122
1199
|
} catch (err) {
|
|
1123
1200
|
// Hook misbehavior must not fail the install — the agent is already
|
|
@@ -1212,6 +1289,7 @@ async function installExistingOpenclawBinding(
|
|
|
1212
1289
|
hubUrl: credentials.hubUrl,
|
|
1213
1290
|
...(credentials.displayName ? { displayName: credentials.displayName } : {}),
|
|
1214
1291
|
...(credentials.runtime ? { runtime: credentials.runtime } : {}),
|
|
1292
|
+
...(credentials.hermesProfile ? { hermesProfile: credentials.hermesProfile } : {}),
|
|
1215
1293
|
});
|
|
1216
1294
|
} catch (err) {
|
|
1217
1295
|
daemonLog.error("provision.onAgentInstalled threw — caches may be stale", {
|
package/src/runtime-models.ts
CHANGED
|
@@ -68,6 +68,37 @@ const KIMI_FALLBACK_MODELS: RuntimeModelProbe[] = [
|
|
|
68
68
|
{ id: "kimi-k2-0711", displayName: "kimi-k2-0711", provider: "kimi", source: "builtin" },
|
|
69
69
|
];
|
|
70
70
|
|
|
71
|
+
// Gemini CLI has no `gemini models list` command — the model set is
|
|
72
|
+
// hard-coded inside the bundle's `VALID_GEMINI_MODELS` (see `@google/gemini-cli`
|
|
73
|
+
// bundle `chunk-BE42OOYM.js:275832`). We mirror the documented user-facing
|
|
74
|
+
// names here as a static catalog; the actual availability depends on the
|
|
75
|
+
// user's auth tier (gcloud ADC / Vertex / GEMINI_API_KEY) and project quota.
|
|
76
|
+
// The `auto` alias is the default — it lets gemini pick the best model.
|
|
77
|
+
const GEMINI_FALLBACK_MODELS: RuntimeModelProbe[] = [
|
|
78
|
+
{ id: "auto", displayName: "Auto (let Gemini pick)", provider: "google", source: "builtin", isDefault: true },
|
|
79
|
+
{ id: "pro", displayName: "Pro alias", provider: "google", source: "builtin" },
|
|
80
|
+
{ id: "flash", displayName: "Flash alias", provider: "google", source: "builtin" },
|
|
81
|
+
{ id: "flash-lite", displayName: "Flash Lite alias", provider: "google", source: "builtin" },
|
|
82
|
+
{ id: "gemini-2.5-pro", displayName: "Gemini 2.5 Pro", provider: "google", source: "builtin" },
|
|
83
|
+
{ id: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash", provider: "google", source: "builtin" },
|
|
84
|
+
{ id: "gemini-2.5-flash-lite", displayName: "Gemini 2.5 Flash Lite", provider: "google", source: "builtin" },
|
|
85
|
+
{ id: "gemini-3-pro-preview", displayName: "Gemini 3 Pro (preview)", provider: "google", source: "builtin" },
|
|
86
|
+
{ id: "gemini-3-flash-preview", displayName: "Gemini 3 Flash (preview)", provider: "google", source: "builtin" },
|
|
87
|
+
{ id: "gemini-3.1-pro-preview", displayName: "Gemini 3.1 Pro (preview)", provider: "google", source: "builtin" },
|
|
88
|
+
{
|
|
89
|
+
id: "gemini-3.1-pro-preview-customtools",
|
|
90
|
+
displayName: "Gemini 3.1 Pro Custom Tools (preview)",
|
|
91
|
+
provider: "google",
|
|
92
|
+
source: "builtin",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "gemini-3.1-flash-lite-preview",
|
|
96
|
+
displayName: "Gemini 3.1 Flash Lite (preview)",
|
|
97
|
+
provider: "google",
|
|
98
|
+
source: "builtin",
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
71
102
|
export interface RuntimeModelDiscovery {
|
|
72
103
|
models?: RuntimeModelProbe[];
|
|
73
104
|
parameters?: RuntimeParameterProbe[];
|
|
@@ -160,6 +191,22 @@ function runtimeCatalogStrategy(entry: RuntimeProbeEntry): RuntimeCatalogStrateg
|
|
|
160
191
|
],
|
|
161
192
|
}),
|
|
162
193
|
};
|
|
194
|
+
case "gemini":
|
|
195
|
+
// Gemini CLI exposes no runtime discovery command and its thinking
|
|
196
|
+
// budget / level (see bundle `ThinkingLevel`, `thinkingBudget`) is
|
|
197
|
+
// configured per-installation in `~/.gemini/settings.json`, not via
|
|
198
|
+
// any CLI flag — so we ship a static model catalog with no parameter
|
|
199
|
+
// controls. settings.json + BOTCORD_GEMINI_BIN feed the cache key so
|
|
200
|
+
// user-side reconfig (e.g. switching auth type) busts the cache.
|
|
201
|
+
return {
|
|
202
|
+
id: entry.id,
|
|
203
|
+
contextKey: runtimeCatalogContextKey(entry, {
|
|
204
|
+
settings: fileStatKey(path.join(homedir(), ".gemini", "settings.json")),
|
|
205
|
+
env: pickEnv(["BOTCORD_GEMINI_BIN", "GEMINI_CLI_HOME"]),
|
|
206
|
+
}),
|
|
207
|
+
discoverFresh: () => ({ models: GEMINI_FALLBACK_MODELS.slice() }),
|
|
208
|
+
fallback: () => ({ models: GEMINI_FALLBACK_MODELS.slice() }),
|
|
209
|
+
};
|
|
163
210
|
default:
|
|
164
211
|
return null;
|
|
165
212
|
}
|
package/src/skill-index.ts
CHANGED
|
@@ -8,8 +8,10 @@ import { homedir } from "node:os";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import {
|
|
10
10
|
agentCodexHomeDir,
|
|
11
|
+
agentHermesHomeDir,
|
|
11
12
|
agentWorkspaceDir,
|
|
12
13
|
} from "./agent-workspace.js";
|
|
14
|
+
import { hermesProfileHomeDir } from "./gateway/runtimes/hermes-agent.js";
|
|
13
15
|
|
|
14
16
|
const MAX_SKILLS = 24;
|
|
15
17
|
const MAX_DESCRIPTION_CHARS = 260;
|
|
@@ -19,6 +21,8 @@ export interface SoftSkillEntry {
|
|
|
19
21
|
name: string;
|
|
20
22
|
path: string;
|
|
21
23
|
source: string;
|
|
24
|
+
runtime?: string;
|
|
25
|
+
profile?: string;
|
|
22
26
|
description?: string;
|
|
23
27
|
mtimeMs: number;
|
|
24
28
|
}
|
|
@@ -26,53 +30,89 @@ export interface SoftSkillEntry {
|
|
|
26
30
|
export interface AgentSkillSnapshotEntry {
|
|
27
31
|
name: string;
|
|
28
32
|
source: string;
|
|
33
|
+
sourceDetail?: string;
|
|
34
|
+
runtime?: string;
|
|
35
|
+
path?: string;
|
|
36
|
+
profile?: string;
|
|
29
37
|
description?: string;
|
|
30
38
|
mtimeMs: number;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
export interface AgentSkillSnapshot {
|
|
34
42
|
agentId: string;
|
|
43
|
+
runtime?: string;
|
|
35
44
|
skills: AgentSkillSnapshotEntry[];
|
|
36
45
|
probedAt: number;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export interface SkillIndexOptions {
|
|
40
49
|
extraDirs?: string[];
|
|
50
|
+
hermesProfile?: string;
|
|
41
51
|
includeGlobal?: boolean;
|
|
42
52
|
runtime?: string;
|
|
43
53
|
}
|
|
44
54
|
|
|
55
|
+
interface SkillRoot {
|
|
56
|
+
dir: string;
|
|
57
|
+
source: string;
|
|
58
|
+
runtime?: string;
|
|
59
|
+
profile?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
export function defaultSkillDirs(
|
|
46
63
|
agentId: string,
|
|
47
64
|
opts: SkillIndexOptions = {},
|
|
48
|
-
):
|
|
65
|
+
): SkillRoot[] {
|
|
49
66
|
const includeGlobal = opts.includeGlobal !== false;
|
|
50
67
|
const agentClaude = {
|
|
51
68
|
dir: path.join(agentWorkspaceDir(agentId), ".claude", "skills"),
|
|
52
69
|
source: "agent-claude",
|
|
70
|
+
runtime: "claude-code",
|
|
53
71
|
};
|
|
54
72
|
const agentCodex = {
|
|
55
73
|
dir: path.join(agentCodexHomeDir(agentId), "skills"),
|
|
56
74
|
source: "agent-codex",
|
|
75
|
+
runtime: "codex",
|
|
57
76
|
};
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
const agentHermes = hermesSkillRoot(agentId, opts.hermesProfile);
|
|
78
|
+
|
|
79
|
+
const dirs: SkillRoot[] = [];
|
|
80
|
+
switch (runtimeFamily(opts.runtime)) {
|
|
81
|
+
case "codex":
|
|
82
|
+
dirs.push(agentCodex);
|
|
83
|
+
if (includeGlobal) {
|
|
84
|
+
dirs.push({
|
|
85
|
+
dir: path.join(homedir(), ".codex", "skills"),
|
|
86
|
+
source: "global-codex",
|
|
87
|
+
runtime: "codex",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
case "hermes":
|
|
92
|
+
dirs.push(agentHermes);
|
|
93
|
+
break;
|
|
94
|
+
case "claude":
|
|
95
|
+
dirs.push(agentClaude);
|
|
96
|
+
if (includeGlobal) {
|
|
97
|
+
dirs.push({
|
|
98
|
+
dir: path.join(homedir(), ".claude", "skills"),
|
|
99
|
+
source: "global-claude",
|
|
100
|
+
runtime: "claude-code",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case "other":
|
|
105
|
+
break;
|
|
71
106
|
}
|
|
72
107
|
|
|
73
108
|
const envDirs = parseSkillDirsEnv(process.env.BOTCORD_SKILL_DIRS);
|
|
74
109
|
for (const dir of [...envDirs, ...(opts.extraDirs ?? [])]) {
|
|
75
|
-
dirs.push({
|
|
110
|
+
dirs.push({
|
|
111
|
+
dir,
|
|
112
|
+
source: "external",
|
|
113
|
+
...(opts.runtime ? { runtime: opts.runtime } : {}),
|
|
114
|
+
...(opts.hermesProfile ? { profile: opts.hermesProfile } : {}),
|
|
115
|
+
});
|
|
76
116
|
}
|
|
77
117
|
|
|
78
118
|
return dedupeDirs(expandSkillRoots(dirs));
|
|
@@ -114,6 +154,8 @@ export function scanSoftSkills(
|
|
|
114
154
|
name: parsed.name,
|
|
115
155
|
path: skillMd,
|
|
116
156
|
source: root.source,
|
|
157
|
+
...(root.runtime ? { runtime: root.runtime } : {}),
|
|
158
|
+
...(root.profile ? { profile: root.profile } : {}),
|
|
117
159
|
description: parsed.description,
|
|
118
160
|
mtimeMs: st.mtimeMs,
|
|
119
161
|
};
|
|
@@ -133,9 +175,14 @@ export function collectAgentSkillSnapshot(
|
|
|
133
175
|
): AgentSkillSnapshot {
|
|
134
176
|
return {
|
|
135
177
|
agentId,
|
|
178
|
+
...(opts.runtime ? { runtime: opts.runtime } : {}),
|
|
136
179
|
skills: scanSoftSkills(agentId, opts).map((skill) => ({
|
|
137
180
|
name: skill.name,
|
|
138
|
-
source: skill.source
|
|
181
|
+
source: snapshotSource(skill.source),
|
|
182
|
+
sourceDetail: skill.source,
|
|
183
|
+
...(skill.runtime ? { runtime: skill.runtime } : {}),
|
|
184
|
+
path: skill.path,
|
|
185
|
+
...(skill.profile ? { profile: skill.profile } : {}),
|
|
139
186
|
...(skill.description ? { description: skill.description } : {}),
|
|
140
187
|
mtimeMs: skill.mtimeMs,
|
|
141
188
|
})),
|
|
@@ -217,68 +264,77 @@ function parseSkillDirsEnv(value: string | undefined): string[] {
|
|
|
217
264
|
}
|
|
218
265
|
|
|
219
266
|
function dedupeDirs(
|
|
220
|
-
dirs:
|
|
221
|
-
):
|
|
267
|
+
dirs: SkillRoot[],
|
|
268
|
+
): SkillRoot[] {
|
|
222
269
|
const seen = new Set<string>();
|
|
223
|
-
const out:
|
|
270
|
+
const out: SkillRoot[] = [];
|
|
224
271
|
for (const entry of dirs) {
|
|
225
272
|
const resolved = path.resolve(entry.dir);
|
|
226
273
|
if (seen.has(resolved)) continue;
|
|
227
274
|
seen.add(resolved);
|
|
228
|
-
out.push({ dir: resolved
|
|
275
|
+
out.push({ ...entry, dir: resolved });
|
|
229
276
|
}
|
|
230
277
|
return out;
|
|
231
278
|
}
|
|
232
279
|
|
|
233
|
-
function expandSkillRoots(
|
|
234
|
-
|
|
235
|
-
): Array<{ dir: string; source: string }> {
|
|
236
|
-
const out: Array<{ dir: string; source: string }> = [];
|
|
280
|
+
function expandSkillRoots(dirs: SkillRoot[]): SkillRoot[] {
|
|
281
|
+
const out: SkillRoot[] = [];
|
|
237
282
|
for (const entry of dirs) {
|
|
238
283
|
out.push(entry);
|
|
239
284
|
if (entry.source.includes("codex")) {
|
|
240
|
-
out.push({ dir: path.join(entry.dir, ".system")
|
|
285
|
+
out.push({ ...entry, dir: path.join(entry.dir, ".system") });
|
|
241
286
|
}
|
|
242
287
|
}
|
|
243
288
|
return out;
|
|
244
289
|
}
|
|
245
290
|
|
|
246
|
-
function
|
|
291
|
+
function hermesSkillRoot(agentId: string, profile: string | undefined): SkillRoot {
|
|
292
|
+
if (profile) {
|
|
293
|
+
try {
|
|
294
|
+
return {
|
|
295
|
+
dir: path.join(hermesProfileHomeDir(profile), "skills"),
|
|
296
|
+
source: "agent-hermes-profile",
|
|
297
|
+
runtime: "hermes-agent",
|
|
298
|
+
profile,
|
|
299
|
+
};
|
|
300
|
+
} catch {
|
|
301
|
+
// Corrupt legacy credentials should not make the whole skill snapshot fail.
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
dir: path.join(agentHermesHomeDir(agentId), "skills"),
|
|
306
|
+
source: "agent-hermes",
|
|
307
|
+
runtime: "hermes-agent",
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function runtimeFamily(runtime: string | undefined): "codex" | "claude" | "hermes" | "other" {
|
|
247
312
|
if (runtime === "codex") return "codex";
|
|
313
|
+
if (runtime === "hermes-agent") return "hermes";
|
|
314
|
+
if (!runtime) return "claude";
|
|
248
315
|
if (runtime === "claude-code") return "claude";
|
|
249
316
|
return "other";
|
|
250
317
|
}
|
|
251
318
|
|
|
252
|
-
function priority(source: string,
|
|
253
|
-
if (runtimeFamily(runtime) === "codex") {
|
|
254
|
-
switch (source) {
|
|
255
|
-
case "agent-codex":
|
|
256
|
-
return 0;
|
|
257
|
-
case "global-codex":
|
|
258
|
-
return 1;
|
|
259
|
-
case "agent-claude":
|
|
260
|
-
return 2;
|
|
261
|
-
case "global-claude":
|
|
262
|
-
return 3;
|
|
263
|
-
default:
|
|
264
|
-
return 4;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
319
|
+
function priority(source: string, _runtime: string | undefined): number {
|
|
268
320
|
switch (source) {
|
|
269
321
|
case "agent-claude":
|
|
270
|
-
return 0;
|
|
271
322
|
case "agent-codex":
|
|
272
|
-
|
|
323
|
+
case "agent-hermes":
|
|
324
|
+
case "agent-hermes-profile":
|
|
325
|
+
return 0;
|
|
273
326
|
case "global-claude":
|
|
274
|
-
return 2;
|
|
275
327
|
case "global-codex":
|
|
276
|
-
return
|
|
328
|
+
return 1;
|
|
277
329
|
default:
|
|
278
|
-
return
|
|
330
|
+
return 2;
|
|
279
331
|
}
|
|
280
332
|
}
|
|
281
333
|
|
|
334
|
+
function snapshotSource(source: string): "workspace" | "runtime-global" {
|
|
335
|
+
return source.startsWith("agent-") ? "workspace" : "runtime-global";
|
|
336
|
+
}
|
|
337
|
+
|
|
282
338
|
function unquote(value: string): string {
|
|
283
339
|
const trimmed = value.trim();
|
|
284
340
|
if (
|