@agentex/agent 0.0.24 → 0.0.26
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/CHANGELOG.md +338 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/derived.d.ts +5 -3
- package/dist/derived.d.ts.map +1 -1
- package/dist/derived.js +11 -7
- package/dist/derived.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/acp/index.d.ts +1 -1
- package/dist/providers/acp/index.d.ts.map +1 -1
- package/dist/providers/acp/index.js +5 -97
- package/dist/providers/acp/index.js.map +1 -1
- package/dist/providers/acp/session.d.ts +8 -1
- package/dist/providers/acp/session.d.ts.map +1 -1
- package/dist/providers/acp/session.js +94 -0
- package/dist/providers/acp/session.js.map +1 -1
- package/dist/providers/claude/attach.d.ts +8 -0
- package/dist/providers/claude/attach.d.ts.map +1 -0
- package/dist/providers/claude/attach.js +113 -0
- package/dist/providers/claude/attach.js.map +1 -0
- package/dist/providers/claude/goal-capability.d.ts +15 -0
- package/dist/providers/claude/goal-capability.d.ts.map +1 -0
- package/dist/providers/claude/goal-capability.js +20 -0
- package/dist/providers/claude/goal-capability.js.map +1 -0
- package/dist/providers/claude/index.d.ts.map +1 -1
- package/dist/providers/claude/index.js +8 -4
- package/dist/providers/claude/index.js.map +1 -1
- package/dist/providers/claude/session.d.ts +11 -9
- package/dist/providers/claude/session.d.ts.map +1 -1
- package/dist/providers/claude/session.js +29 -14
- package/dist/providers/claude/session.js.map +1 -1
- package/dist/providers/codex/attach.d.ts +9 -0
- package/dist/providers/codex/attach.d.ts.map +1 -0
- package/dist/providers/codex/attach.js +93 -0
- package/dist/providers/codex/attach.js.map +1 -0
- package/dist/providers/codex/goal-capability.d.ts +13 -0
- package/dist/providers/codex/goal-capability.d.ts.map +1 -0
- package/dist/providers/codex/goal-capability.js +18 -0
- package/dist/providers/codex/goal-capability.js.map +1 -0
- package/dist/providers/codex/index.d.ts +1 -0
- package/dist/providers/codex/index.d.ts.map +1 -1
- package/dist/providers/codex/index.js +9 -6
- package/dist/providers/codex/index.js.map +1 -1
- package/dist/providers/codex/session.d.ts +11 -7
- package/dist/providers/codex/session.d.ts.map +1 -1
- package/dist/providers/codex/session.js +24 -12
- package/dist/providers/codex/session.js.map +1 -1
- package/dist/providers/codex/transcript-normalize.d.ts +28 -0
- package/dist/providers/codex/transcript-normalize.d.ts.map +1 -0
- package/dist/providers/codex/transcript-normalize.js +191 -0
- package/dist/providers/codex/transcript-normalize.js.map +1 -0
- package/dist/providers/cursor/index.d.ts.map +1 -1
- package/dist/providers/cursor/index.js +2 -2
- package/dist/providers/cursor/index.js.map +1 -1
- package/dist/providers/openclaw/index.d.ts.map +1 -1
- package/dist/providers/openclaw/index.js +2 -2
- package/dist/providers/openclaw/index.js.map +1 -1
- package/dist/providers/opencode/index.d.ts.map +1 -1
- package/dist/providers/opencode/index.js +3 -5
- package/dist/providers/opencode/index.js.map +1 -1
- package/dist/providers/pi/index.d.ts.map +1 -1
- package/dist/providers/pi/index.js +3 -5
- package/dist/providers/pi/index.js.map +1 -1
- package/dist/providers/process/index.d.ts.map +1 -1
- package/dist/providers/process/index.js +2 -2
- package/dist/providers/process/index.js.map +1 -1
- package/dist/registry.d.ts +0 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +0 -4
- package/dist/registry.js.map +1 -1
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/record.d.ts +43 -0
- package/dist/sessions/record.d.ts.map +1 -0
- package/dist/sessions/record.js +85 -0
- package/dist/sessions/record.js.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/uuid.d.ts +7 -1
- package/dist/utils/uuid.d.ts.map +1 -1
- package/dist/utils/uuid.js +21 -1
- package/dist/utils/uuid.js.map +1 -1
- package/package.json +64 -7
- package/src/derived.ts +311 -0
- package/src/goals/controller.ts +442 -0
- package/src/goals/index.ts +21 -0
- package/src/goals/normalize.ts +173 -0
- package/src/goals/sentinel.ts +90 -0
- package/src/index.ts +270 -0
- package/src/providers/_shared/http-agent.ts +304 -0
- package/src/providers/acp/index.ts +103 -0
- package/src/providers/acp/parse.ts +131 -0
- package/src/providers/acp/session.ts +744 -0
- package/src/providers/claude/attach.ts +147 -0
- package/src/providers/claude/codec.ts +43 -0
- package/src/providers/claude/execute.ts +300 -0
- package/src/providers/claude/goal-capability.ts +21 -0
- package/src/providers/claude/index.ts +72 -0
- package/src/providers/claude/mcp.ts +82 -0
- package/src/providers/claude/parse.ts +824 -0
- package/src/providers/claude/session.ts +1192 -0
- package/src/providers/claude/transcript.ts +555 -0
- package/src/providers/codex/attach.ts +123 -0
- package/src/providers/codex/codec.ts +50 -0
- package/src/providers/codex/execute.ts +337 -0
- package/src/providers/codex/goal-capability.ts +19 -0
- package/src/providers/codex/index.ts +57 -0
- package/src/providers/codex/modes.ts +159 -0
- package/src/providers/codex/parse.ts +691 -0
- package/src/providers/codex/plan-mode.ts +49 -0
- package/src/providers/codex/session.ts +1287 -0
- package/src/providers/codex/transcript-normalize.ts +197 -0
- package/src/providers/codex/transcript.ts +487 -0
- package/src/providers/codex/usage-scanner.ts +178 -0
- package/src/providers/copilot/index.ts +19 -0
- package/src/providers/cursor/codec.ts +44 -0
- package/src/providers/cursor/execute.ts +271 -0
- package/src/providers/cursor/index.ts +25 -0
- package/src/providers/cursor/parse.ts +288 -0
- package/src/providers/gemini/index.ts +21 -0
- package/src/providers/openclaw/codec.ts +40 -0
- package/src/providers/openclaw/execute.ts +19 -0
- package/src/providers/openclaw/index.ts +29 -0
- package/src/providers/opencode/codec.ts +50 -0
- package/src/providers/opencode/event-parse.ts +141 -0
- package/src/providers/opencode/execute.ts +251 -0
- package/src/providers/opencode/http-session.ts +427 -0
- package/src/providers/opencode/index.ts +30 -0
- package/src/providers/opencode/parse.ts +203 -0
- package/src/providers/opencode/server.ts +0 -0
- package/src/providers/pi/codec.ts +44 -0
- package/src/providers/pi/execute.ts +297 -0
- package/src/providers/pi/index.ts +30 -0
- package/src/providers/pi/parse.ts +231 -0
- package/src/providers/pi/session.ts +381 -0
- package/src/providers/process/execute.ts +148 -0
- package/src/providers/process/index.ts +52 -0
- package/src/registry.ts +40 -0
- package/src/sessions/index.ts +8 -0
- package/src/sessions/record.ts +108 -0
- package/src/types.ts +1638 -0
- package/src/utils/ask-user-question.ts +57 -0
- package/src/utils/auth.ts +661 -0
- package/src/utils/binary.ts +179 -0
- package/src/utils/endpoint.ts +172 -0
- package/src/utils/env.ts +63 -0
- package/src/utils/execute-all.ts +68 -0
- package/src/utils/exit-plan-mode.ts +40 -0
- package/src/utils/instructions.ts +427 -0
- package/src/utils/process.ts +223 -0
- package/src/utils/runtime-config.ts +100 -0
- package/src/utils/runtime-homes.ts +49 -0
- package/src/utils/skill-commands.ts +493 -0
- package/src/utils/skills.ts +500 -0
- package/src/utils/template.ts +16 -0
- package/src/utils/tool-names.ts +51 -0
- package/src/utils/uuid.ts +21 -0
- package/src/utils/workspace.ts +156 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
|
|
5
|
+
export type SkillRuntime = "claude" | "codex" | "gemini" | "cursor" | "opencode" | "pi";
|
|
6
|
+
|
|
7
|
+
export type SkillLocation = "global" | "workspace";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The two standard skill discovery channels:
|
|
11
|
+
* - "agents": ~/.agents/skills/ or {cwd}/.agents/skills/ — scanned by Codex, Gemini, Cursor, OpenCode, Pi
|
|
12
|
+
* - "claude": ~/.claude/skills/ or {cwd}/.claude/skills/ — scanned by Claude Code (the only agent that doesn't scan .agents/)
|
|
13
|
+
*/
|
|
14
|
+
export type SkillChannel = "agents" | "claude";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Native per-runtime directories. Only used when includeNativeDirs is enabled.
|
|
18
|
+
* These are in addition to the two standard channels.
|
|
19
|
+
* Claude and Codex are excluded since they ARE the standard channels.
|
|
20
|
+
*/
|
|
21
|
+
const NATIVE_DIRS: Record<string, { global: string; workspace: (cwd: string) => string }> = {
|
|
22
|
+
gemini: {
|
|
23
|
+
global: path.join(os.homedir(), ".gemini", "skills"),
|
|
24
|
+
workspace: (cwd) => path.join(cwd, ".gemini", "skills"),
|
|
25
|
+
},
|
|
26
|
+
cursor: {
|
|
27
|
+
global: path.join(os.homedir(), ".cursor", "skills"),
|
|
28
|
+
workspace: (cwd) => path.join(cwd, ".cursor", "skills"),
|
|
29
|
+
},
|
|
30
|
+
opencode: {
|
|
31
|
+
global: path.join(os.homedir(), ".config", "opencode", "skills"),
|
|
32
|
+
workspace: (cwd) => path.join(cwd, ".opencode", "skills"),
|
|
33
|
+
},
|
|
34
|
+
pi: {
|
|
35
|
+
global: path.join(os.homedir(), ".pi", "agent", "skills"),
|
|
36
|
+
workspace: (cwd) => path.join(cwd, ".pi", "skills"),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Public API types
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
export interface InstallSkillsOptions {
|
|
45
|
+
/** Where to install skills. Defaults to "global" (home directory).
|
|
46
|
+
* - "global": ~/.agents/skills/ + ~/.claude/skills/
|
|
47
|
+
* - "workspace": {cwd}/.agents/skills/ + {cwd}/.claude/skills/ */
|
|
48
|
+
location?: SkillLocation;
|
|
49
|
+
/** Working directory. Required for "workspace" location. */
|
|
50
|
+
cwd?: string;
|
|
51
|
+
/** Also install into each runtime's native directory (e.g. ~/.gemini/skills/, ~/.cursor/skills/).
|
|
52
|
+
* Usually unnecessary since most runtimes scan .agents/skills/. Default: false. */
|
|
53
|
+
includeNativeDirs?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SkillInstallEntry {
|
|
57
|
+
target: string;
|
|
58
|
+
skillName: string;
|
|
59
|
+
status: "created" | "skipped" | "conflict" | "error";
|
|
60
|
+
targetPath: string;
|
|
61
|
+
error?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SkillInstallResult {
|
|
65
|
+
entries: SkillInstallEntry[];
|
|
66
|
+
installed: number;
|
|
67
|
+
skipped: number;
|
|
68
|
+
conflicts: number;
|
|
69
|
+
errors: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RemoveSkillsOptions {
|
|
73
|
+
/** Where to remove skills from. Defaults to "global". */
|
|
74
|
+
location?: SkillLocation;
|
|
75
|
+
/** Working directory. Required for "workspace" location. */
|
|
76
|
+
cwd?: string;
|
|
77
|
+
/** Also remove from native runtime directories. Default: false. */
|
|
78
|
+
includeNativeDirs?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface SkillRemoveEntry {
|
|
82
|
+
target: string;
|
|
83
|
+
skillName: string;
|
|
84
|
+
status: "removed" | "not_found" | "conflict" | "error";
|
|
85
|
+
targetPath: string;
|
|
86
|
+
error?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface SkillRemoveResult {
|
|
90
|
+
entries: SkillRemoveEntry[];
|
|
91
|
+
removed: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface InstalledSkill {
|
|
95
|
+
name: string;
|
|
96
|
+
/** Directory containing SKILL.md. Symlinks are resolved to their target directory. */
|
|
97
|
+
sourcePath: string | null;
|
|
98
|
+
isSymlink: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Path resolution
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/** Resolve the standard skill discovery paths (both channels). */
|
|
106
|
+
function resolveStandardPaths(location: SkillLocation, cwd?: string): Array<{ target: string; skillsHome: string }> {
|
|
107
|
+
if (location === "workspace") {
|
|
108
|
+
if (!cwd) throw new Error("cwd is required when location is 'workspace'");
|
|
109
|
+
return [
|
|
110
|
+
{ target: "agents", skillsHome: path.join(cwd, ".agents", "skills") },
|
|
111
|
+
{ target: "claude", skillsHome: path.join(cwd, ".claude", "skills") },
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
return [
|
|
115
|
+
{ target: "agents", skillsHome: path.join(os.homedir(), ".agents", "skills") },
|
|
116
|
+
{ target: "claude", skillsHome: path.join(os.homedir(), ".claude", "skills") },
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Resolve native per-runtime paths (for includeNativeDirs). */
|
|
121
|
+
function resolveNativePaths(location: SkillLocation, cwd?: string): Array<{ target: string; skillsHome: string }> {
|
|
122
|
+
const results: Array<{ target: string; skillsHome: string }> = [];
|
|
123
|
+
for (const [runtime, dirs] of Object.entries(NATIVE_DIRS)) {
|
|
124
|
+
if (location === "workspace") {
|
|
125
|
+
if (!cwd) continue;
|
|
126
|
+
results.push({ target: runtime, skillsHome: dirs.workspace(cwd) });
|
|
127
|
+
} else {
|
|
128
|
+
results.push({ target: runtime, skillsHome: dirs.global });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return results;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Build the full list of install/remove targets. */
|
|
135
|
+
function resolveTargets(
|
|
136
|
+
location: SkillLocation,
|
|
137
|
+
cwd?: string,
|
|
138
|
+
includeNativeDirs?: boolean,
|
|
139
|
+
): Array<{ target: string; skillsHome: string }> {
|
|
140
|
+
const targets = resolveStandardPaths(location, cwd);
|
|
141
|
+
if (includeNativeDirs) {
|
|
142
|
+
targets.push(...resolveNativePaths(location, cwd));
|
|
143
|
+
}
|
|
144
|
+
return targets;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Public API
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Install skill directories into agent discovery paths via idempotent symlinks.
|
|
153
|
+
*
|
|
154
|
+
* By default, installs into the two standard channels:
|
|
155
|
+
* - `.agents/skills/` — discovered by Codex, Gemini, Cursor, OpenCode, Pi
|
|
156
|
+
* - `.claude/skills/` — discovered by Claude Code
|
|
157
|
+
*
|
|
158
|
+
* Set `includeNativeDirs: true` to also install into each runtime's
|
|
159
|
+
* native directory (e.g. `.gemini/skills/`, `.cursor/skills/`).
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* // Global install (home directory)
|
|
164
|
+
* await installSkills(["~/.myapp/skills/code-review"]);
|
|
165
|
+
* // Creates ~/.agents/skills/code-review + ~/.claude/skills/code-review
|
|
166
|
+
*
|
|
167
|
+
* // Workspace install (project-local)
|
|
168
|
+
* await installSkills(skillDirs, { location: "workspace", cwd: "/path/to/project" });
|
|
169
|
+
* // Creates {cwd}/.agents/skills/... + {cwd}/.claude/skills/...
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export async function installSkills(
|
|
173
|
+
skillDirs: string[],
|
|
174
|
+
options?: InstallSkillsOptions,
|
|
175
|
+
): Promise<SkillInstallResult> {
|
|
176
|
+
const entries: SkillInstallEntry[] = [];
|
|
177
|
+
const location = options?.location ?? "global";
|
|
178
|
+
const targets = resolveTargets(location, options?.cwd, options?.includeNativeDirs);
|
|
179
|
+
|
|
180
|
+
for (const { target, skillsHome } of targets) {
|
|
181
|
+
await fs.mkdir(skillsHome, { recursive: true });
|
|
182
|
+
|
|
183
|
+
for (const dir of skillDirs) {
|
|
184
|
+
const resolved = path.resolve(dir);
|
|
185
|
+
const name = path.basename(resolved);
|
|
186
|
+
const symlink = path.join(skillsHome, name);
|
|
187
|
+
try {
|
|
188
|
+
const status = await ensureSkillSymlink(resolved, symlink);
|
|
189
|
+
entries.push({ target, skillName: name, status, targetPath: symlink });
|
|
190
|
+
} catch (err) {
|
|
191
|
+
entries.push({
|
|
192
|
+
target,
|
|
193
|
+
skillName: name,
|
|
194
|
+
status: "error",
|
|
195
|
+
targetPath: symlink,
|
|
196
|
+
error: err instanceof Error ? err.message : String(err),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
entries,
|
|
204
|
+
installed: entries.filter((e) => e.status === "created").length,
|
|
205
|
+
skipped: entries.filter((e) => e.status === "skipped").length,
|
|
206
|
+
conflicts: entries.filter((e) => e.status === "conflict").length,
|
|
207
|
+
errors: entries.filter((e) => e.status === "error").length,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Remove skill symlinks from agent discovery paths.
|
|
213
|
+
*
|
|
214
|
+
* Only removes symlinks that point to the specified source directories.
|
|
215
|
+
* User-installed skills or skills from other apps are never touched.
|
|
216
|
+
*/
|
|
217
|
+
export async function removeSkills(
|
|
218
|
+
skillDirs: string[],
|
|
219
|
+
options?: RemoveSkillsOptions,
|
|
220
|
+
): Promise<SkillRemoveResult> {
|
|
221
|
+
const entries: SkillRemoveEntry[] = [];
|
|
222
|
+
const location = options?.location ?? "global";
|
|
223
|
+
const targets = resolveTargets(location, options?.cwd, options?.includeNativeDirs);
|
|
224
|
+
|
|
225
|
+
for (const { target, skillsHome } of targets) {
|
|
226
|
+
for (const dir of skillDirs) {
|
|
227
|
+
const resolved = path.resolve(dir);
|
|
228
|
+
const name = path.basename(resolved);
|
|
229
|
+
const symlink = path.join(skillsHome, name);
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const stat = await fs.lstat(symlink);
|
|
233
|
+
if (!stat.isSymbolicLink()) {
|
|
234
|
+
entries.push({ target, skillName: name, status: "conflict", targetPath: symlink });
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const existing = await fs.readlink(symlink);
|
|
238
|
+
const resolvedExisting = path.resolve(path.dirname(symlink), existing);
|
|
239
|
+
if (resolvedExisting !== resolved) {
|
|
240
|
+
entries.push({ target, skillName: name, status: "conflict", targetPath: symlink });
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
await fs.unlink(symlink);
|
|
244
|
+
entries.push({ target, skillName: name, status: "removed", targetPath: symlink });
|
|
245
|
+
} catch (err) {
|
|
246
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
247
|
+
entries.push({ target, skillName: name, status: "not_found", targetPath: symlink });
|
|
248
|
+
} else {
|
|
249
|
+
entries.push({
|
|
250
|
+
target,
|
|
251
|
+
skillName: name,
|
|
252
|
+
status: "error",
|
|
253
|
+
targetPath: symlink,
|
|
254
|
+
error: err instanceof Error ? err.message : String(err),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
entries,
|
|
263
|
+
removed: entries.filter((e) => e.status === "removed").length,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* List skills installed in the standard discovery channels.
|
|
269
|
+
*
|
|
270
|
+
* Returns a record keyed by channel ("agents", "claude") with the skills
|
|
271
|
+
* found in each. If `includeNativeDirs` is true, also includes native
|
|
272
|
+
* runtime directories (keyed by runtime name).
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const skills = await listInstalledSkills();
|
|
277
|
+
* // { agents: [...], claude: [...] }
|
|
278
|
+
*
|
|
279
|
+
* const skills = await listInstalledSkills({ location: "workspace", cwd: "/my/project" });
|
|
280
|
+
* // { agents: [...], claude: [...] }
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
export async function listInstalledSkills(
|
|
284
|
+
options?: { location?: SkillLocation; cwd?: string; includeNativeDirs?: boolean },
|
|
285
|
+
): Promise<Record<string, InstalledSkill[]>> {
|
|
286
|
+
const location = options?.location ?? "global";
|
|
287
|
+
const targets = resolveTargets(location, options?.cwd, options?.includeNativeDirs);
|
|
288
|
+
|
|
289
|
+
const result: Record<string, InstalledSkill[]> = {};
|
|
290
|
+
|
|
291
|
+
for (const { target, skillsHome } of targets) {
|
|
292
|
+
let dirEntries: string[];
|
|
293
|
+
try {
|
|
294
|
+
dirEntries = await fs.readdir(skillsHome);
|
|
295
|
+
} catch {
|
|
296
|
+
result[target] = [];
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const skills: InstalledSkill[] = [];
|
|
301
|
+
for (const name of dirEntries) {
|
|
302
|
+
const fullPath = path.join(skillsHome, name);
|
|
303
|
+
try {
|
|
304
|
+
const stat = await fs.lstat(fullPath);
|
|
305
|
+
if (stat.isSymbolicLink()) {
|
|
306
|
+
const linkTarget = await fs.readlink(fullPath);
|
|
307
|
+
skills.push({
|
|
308
|
+
name,
|
|
309
|
+
sourcePath: path.resolve(path.dirname(fullPath), linkTarget),
|
|
310
|
+
isSymlink: true,
|
|
311
|
+
});
|
|
312
|
+
} else if (stat.isDirectory()) {
|
|
313
|
+
skills.push({ name, sourcePath: fullPath, isSymlink: false });
|
|
314
|
+
}
|
|
315
|
+
} catch {
|
|
316
|
+
// Skip unreadable entries
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
result[target] = skills;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// Convenience resolvers (exported for low-level use / escape hatch)
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Resolve the global (home-directory) skills path for a standard channel.
|
|
331
|
+
*/
|
|
332
|
+
export function resolveSkillsHome(channel: SkillChannel): string {
|
|
333
|
+
switch (channel) {
|
|
334
|
+
case "agents":
|
|
335
|
+
return path.join(os.homedir(), ".agents", "skills");
|
|
336
|
+
case "claude":
|
|
337
|
+
return path.join(os.homedir(), ".claude", "skills");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Resolve the workspace-relative skills path for a standard channel.
|
|
343
|
+
*/
|
|
344
|
+
export function resolveSkillsWorkspace(channel: SkillChannel, cwd: string): string {
|
|
345
|
+
switch (channel) {
|
|
346
|
+
case "agents":
|
|
347
|
+
return path.join(cwd, ".agents", "skills");
|
|
348
|
+
case "claude":
|
|
349
|
+
return path.join(cwd, ".claude", "skills");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Resolve the native per-runtime skills path (home directory).
|
|
355
|
+
* Returns null for runtimes that use standard channels (claude → .claude/, codex → .agents/).
|
|
356
|
+
*/
|
|
357
|
+
export function resolveNativeSkillsHome(runtime: SkillRuntime): string | null {
|
|
358
|
+
const entry = NATIVE_DIRS[runtime];
|
|
359
|
+
return entry?.global ?? null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Resolve the native per-runtime skills path (workspace).
|
|
364
|
+
* Returns null for runtimes that use standard channels.
|
|
365
|
+
*/
|
|
366
|
+
export function resolveNativeSkillsWorkspace(runtime: SkillRuntime, cwd: string): string | null {
|
|
367
|
+
const entry = NATIVE_DIRS[runtime];
|
|
368
|
+
return entry ? entry.workspace(cwd) : null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
// Low-level helpers
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Idempotent symlink creation. Returns the outcome:
|
|
377
|
+
* - "created": new symlink was created
|
|
378
|
+
* - "skipped": symlink already exists and points to the correct source
|
|
379
|
+
* - "conflict": symlink exists but points elsewhere (not overwritten)
|
|
380
|
+
*/
|
|
381
|
+
export async function ensureSkillSymlink(
|
|
382
|
+
source: string,
|
|
383
|
+
target: string,
|
|
384
|
+
): Promise<"created" | "skipped" | "conflict"> {
|
|
385
|
+
try {
|
|
386
|
+
const stat = await fs.lstat(target);
|
|
387
|
+
if (stat.isSymbolicLink()) {
|
|
388
|
+
const existing = await fs.readlink(target);
|
|
389
|
+
const resolvedExisting = path.resolve(path.dirname(target), existing);
|
|
390
|
+
if (resolvedExisting === path.resolve(source)) {
|
|
391
|
+
return "skipped";
|
|
392
|
+
}
|
|
393
|
+
return "conflict";
|
|
394
|
+
}
|
|
395
|
+
return "conflict";
|
|
396
|
+
} catch {
|
|
397
|
+
await fs.symlink(source, target, "dir");
|
|
398
|
+
return "created";
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// Internal helpers (used by provider execute functions)
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Place skills into a home-directory-based skills folder via idempotent symlinks.
|
|
408
|
+
* Used internally by provider execute functions for native runtime injection.
|
|
409
|
+
*
|
|
410
|
+
* Returns the skills home path, or null if no skills were provided.
|
|
411
|
+
*/
|
|
412
|
+
export async function injectHomeSkills(
|
|
413
|
+
skillDirs: string[],
|
|
414
|
+
runtime: SkillRuntime,
|
|
415
|
+
): Promise<string | null> {
|
|
416
|
+
if (skillDirs.length === 0) return null;
|
|
417
|
+
|
|
418
|
+
const entry = NATIVE_DIRS[runtime];
|
|
419
|
+
const skillsHome = entry?.global ?? null;
|
|
420
|
+
if (!skillsHome) return null;
|
|
421
|
+
|
|
422
|
+
await fs.mkdir(skillsHome, { recursive: true });
|
|
423
|
+
|
|
424
|
+
for (const dir of skillDirs) {
|
|
425
|
+
const name = path.basename(dir);
|
|
426
|
+
const target = path.join(skillsHome, name);
|
|
427
|
+
try {
|
|
428
|
+
await ensureSkillSymlink(dir, target);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
console.warn(`Failed to inject skill "${name}" into ${skillsHome}: ${err}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return skillsHome;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Place skills into {cwd}/.agents/skills/ via idempotent symlinks.
|
|
439
|
+
* Used internally by codex provider.
|
|
440
|
+
*
|
|
441
|
+
* Returns the skills directory path, or null if no skills were provided.
|
|
442
|
+
*/
|
|
443
|
+
export async function injectWorkspaceSkills(
|
|
444
|
+
skillDirs: string[],
|
|
445
|
+
cwd: string,
|
|
446
|
+
): Promise<string | null> {
|
|
447
|
+
if (skillDirs.length === 0) return null;
|
|
448
|
+
|
|
449
|
+
const skillsHome = path.join(cwd, ".agents", "skills");
|
|
450
|
+
await fs.mkdir(skillsHome, { recursive: true });
|
|
451
|
+
|
|
452
|
+
for (const dir of skillDirs) {
|
|
453
|
+
const name = path.basename(dir);
|
|
454
|
+
const target = path.join(skillsHome, name);
|
|
455
|
+
try {
|
|
456
|
+
await ensureSkillSymlink(dir, target);
|
|
457
|
+
} catch (err) {
|
|
458
|
+
console.warn(`Failed to inject skill "${name}" into ${skillsHome}: ${err}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return skillsHome;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Build an ephemeral tmpdir with .claude/skills/ containing symlinks.
|
|
467
|
+
* Used by Claude provider — passed via --add-dir, cleaned up after execution.
|
|
468
|
+
*
|
|
469
|
+
* Returns the tmpdir path (caller must clean up with cleanupSkillsDir).
|
|
470
|
+
*/
|
|
471
|
+
export async function buildSkillsDir(skillDirs: string[], runtime: SkillRuntime): Promise<string> {
|
|
472
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "agentex-skills-"));
|
|
473
|
+
|
|
474
|
+
let skillsRoot: string;
|
|
475
|
+
if (runtime === "claude") {
|
|
476
|
+
skillsRoot = path.join(tmpDir, ".claude", "skills");
|
|
477
|
+
} else {
|
|
478
|
+
skillsRoot = path.join(tmpDir, "skills");
|
|
479
|
+
}
|
|
480
|
+
await fs.mkdir(skillsRoot, { recursive: true });
|
|
481
|
+
|
|
482
|
+
for (const dir of skillDirs) {
|
|
483
|
+
const name = path.basename(dir);
|
|
484
|
+
try {
|
|
485
|
+
await fs.symlink(dir, path.join(skillsRoot, name), "dir");
|
|
486
|
+
} catch (err) {
|
|
487
|
+
console.warn(`Failed to symlink skill "${name}": ${err}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return tmpDir;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export async function cleanupSkillsDir(tmpDir: string): Promise<void> {
|
|
495
|
+
try {
|
|
496
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
497
|
+
} catch {
|
|
498
|
+
// Best effort cleanup
|
|
499
|
+
}
|
|
500
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function resolvePathValue(obj: unknown, dottedPath: string): unknown {
|
|
2
|
+
const keys = dottedPath.split(".");
|
|
3
|
+
let current = obj;
|
|
4
|
+
for (const key of keys) {
|
|
5
|
+
if (current == null || typeof current !== "object") return undefined;
|
|
6
|
+
current = (current as Record<string, unknown>)[key];
|
|
7
|
+
}
|
|
8
|
+
return current;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function renderTemplate(template: string, data: Record<string, unknown>): string {
|
|
12
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_, path: string) => {
|
|
13
|
+
const value = resolvePathValue(data, path.trim());
|
|
14
|
+
return value == null ? "" : String(value);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { StreamEvent } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Upper bound on remembered `toolCallId → name` entries. A long-lived session
|
|
5
|
+
* can issue thousands of tool calls; without a cap the map would grow without
|
|
6
|
+
* bound. Tool calls and their results are adjacent in the stream, so a
|
|
7
|
+
* generous FIFO window is plenty — we evict the oldest entry once full.
|
|
8
|
+
*/
|
|
9
|
+
const MAX_TRACKED = 4096;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Stateful enricher that fills in `tool_result.toolName`.
|
|
13
|
+
*
|
|
14
|
+
* The per-line parsers (`parseStreamLine`, `parseCodexStreamLine`) are
|
|
15
|
+
* stateless, and Claude delivers a `tool_use` (the call, carrying `name`) and
|
|
16
|
+
* its `tool_result` (carrying only `tool_use_id`) on *separate* wire lines.
|
|
17
|
+
* Correlating the two therefore has to live in a layer that sees the whole
|
|
18
|
+
* event sequence — the session dispatch loop and the exec accumulator. This
|
|
19
|
+
* factory returns a function that remembers each `tool_call`'s name and stamps
|
|
20
|
+
* it onto the matching `tool_result`, so consumers don't have to keep their own
|
|
21
|
+
* cache.
|
|
22
|
+
*
|
|
23
|
+
* Mutates and returns the event for call-site ergonomics (events are freshly
|
|
24
|
+
* parsed objects, never shared). Only fills `toolName` when it's still null, so
|
|
25
|
+
* providers whose parser already knows the name (Codex) keep their value.
|
|
26
|
+
*
|
|
27
|
+
* One tracker instance per session / per exec invocation. Codex's `item_N` ids
|
|
28
|
+
* are turn-local but a call and its result share the same id within a turn, so
|
|
29
|
+
* correlation holds; ids repeating across turns simply overwrite, which is
|
|
30
|
+
* correct.
|
|
31
|
+
*/
|
|
32
|
+
export function createToolNameTracker(): (event: StreamEvent) => StreamEvent {
|
|
33
|
+
const namesById = new Map<string, string>();
|
|
34
|
+
|
|
35
|
+
return (event) => {
|
|
36
|
+
if (event.type === "tool_call") {
|
|
37
|
+
if (event.toolCallId && event.name) {
|
|
38
|
+
if (namesById.size >= MAX_TRACKED) {
|
|
39
|
+
const oldest = namesById.keys().next().value;
|
|
40
|
+
if (oldest !== undefined) namesById.delete(oldest);
|
|
41
|
+
}
|
|
42
|
+
namesById.set(event.toolCallId, event.name);
|
|
43
|
+
}
|
|
44
|
+
} else if (event.type === "tool_result") {
|
|
45
|
+
if (event.toolName == null && event.toolCallId) {
|
|
46
|
+
event.toolName = namesById.get(event.toolCallId) ?? null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return event;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9562 UUIDv7: 48-bit unix-ms timestamp + version/variant bits + 74 random
|
|
3
|
+
* bits. Callers need uniqueness and rough time-sortability, not per-ms
|
|
4
|
+
* monotonic counters. Local implementation replaces the `uuid` dependency
|
|
5
|
+
* (22 runtime modules for one function).
|
|
6
|
+
*/
|
|
7
|
+
export function uuidv7(): string {
|
|
8
|
+
const b = new Uint8Array(16);
|
|
9
|
+
crypto.getRandomValues(b);
|
|
10
|
+
const ts = BigInt(Date.now());
|
|
11
|
+
b[0] = Number((ts >> 40n) & 0xffn);
|
|
12
|
+
b[1] = Number((ts >> 32n) & 0xffn);
|
|
13
|
+
b[2] = Number((ts >> 24n) & 0xffn);
|
|
14
|
+
b[3] = Number((ts >> 16n) & 0xffn);
|
|
15
|
+
b[4] = Number((ts >> 8n) & 0xffn);
|
|
16
|
+
b[5] = Number(ts & 0xffn);
|
|
17
|
+
b[6] = (b[6]! & 0x0f) | 0x70;
|
|
18
|
+
b[8] = (b[8]! & 0x3f) | 0x80;
|
|
19
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
20
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
21
|
+
}
|