@ai-sdk/harness-cline 1.0.18 → 1.0.20
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 +24 -0
- package/README.md +1 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +188 -93
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/cline-auth.ts +23 -9
- package/src/cline-harness.ts +22 -4
- package/src/cline-remote-ops.ts +5 -29
- package/src/cline-session.ts +44 -44
- package/src/cline-skills.ts +173 -48
- package/src/cline-tools.ts +5 -3
- package/src/index.ts +1 -1
package/src/cline-skills.ts
CHANGED
|
@@ -1,59 +1,184 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import type { HarnessV1Skill } from '@ai-sdk/harness';
|
|
3
|
-
import {
|
|
4
|
-
import type
|
|
3
|
+
import type { AgentTool } from '@cline/agents';
|
|
4
|
+
import { createDefaultTools, type ToolExecutors } from '@cline/core';
|
|
5
|
+
|
|
6
|
+
const CLINE_SKILL_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
7
|
+
|
|
8
|
+
type ProjectedClineSkill = {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly description: string;
|
|
12
|
+
readonly content: string;
|
|
13
|
+
readonly files: ReadonlyArray<{
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly content: string;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ClineSkillsRuntime = {
|
|
20
|
+
readonly signature: string;
|
|
21
|
+
readonly tool?: AgentTool;
|
|
22
|
+
};
|
|
5
23
|
|
|
6
24
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
11
|
-
* prompt section (see `renderSkillsPromptSection`) that points at these files.
|
|
25
|
+
* Build Cline's native skills tool around an immutable in-host projection of
|
|
26
|
+
* the harness skills. The low-level Agent does not discover skills itself, so
|
|
27
|
+
* the adapter provides the same tool contract that Cline's core orchestration
|
|
28
|
+
* layer uses without involving the sandbox filesystem.
|
|
12
29
|
*/
|
|
13
|
-
export
|
|
14
|
-
sandbox,
|
|
15
|
-
sandboxHomeDir,
|
|
30
|
+
export function createClineSkillsRuntime({
|
|
16
31
|
skills,
|
|
17
|
-
abortSignal,
|
|
18
32
|
}: {
|
|
19
|
-
sandbox: Experimental_SandboxSession;
|
|
20
|
-
sandboxHomeDir: string;
|
|
21
33
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
...
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
}): ClineSkillsRuntime {
|
|
35
|
+
const projectedSkills = projectClineSkills({ skills });
|
|
36
|
+
const signature = JSON.stringify(projectedSkills);
|
|
37
|
+
if (projectedSkills.length === 0) {
|
|
38
|
+
return { signature };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const executor: NonNullable<ToolExecutors['skills']> = async (
|
|
42
|
+
...executorArguments
|
|
43
|
+
) => {
|
|
44
|
+
const [requestedSkill, args] = executorArguments;
|
|
45
|
+
const skill = projectedSkills.find(
|
|
46
|
+
candidate => candidate.id === normalizeSkillToken(requestedSkill),
|
|
47
|
+
);
|
|
48
|
+
if (skill == null) {
|
|
49
|
+
const availableSkills = projectedSkills.map(skill => skill.name);
|
|
50
|
+
return availableSkills.length > 0
|
|
51
|
+
? `Skill "${requestedSkill}" not found. Available skills: ${availableSkills.join(', ')}`
|
|
52
|
+
: 'No skills are currently available.';
|
|
53
|
+
}
|
|
54
|
+
return renderSkillInstructions({ skill, args });
|
|
55
|
+
};
|
|
56
|
+
executor.configuredSkills = projectedSkills.map(skill => ({
|
|
57
|
+
id: skill.id,
|
|
58
|
+
name: skill.name,
|
|
59
|
+
description: skill.description,
|
|
60
|
+
disabled: false,
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
const tool = createDefaultTools({
|
|
64
|
+
executors: { skills: executor },
|
|
65
|
+
enableReadFiles: false,
|
|
66
|
+
enableSearch: false,
|
|
67
|
+
enableBash: false,
|
|
68
|
+
enableWebFetch: false,
|
|
69
|
+
enableApplyPatch: false,
|
|
70
|
+
enableEditor: false,
|
|
71
|
+
enableSkills: true,
|
|
72
|
+
enableAskQuestion: false,
|
|
73
|
+
enableSubmitAndExit: false,
|
|
74
|
+
}).find(tool => tool.name === 'skills');
|
|
75
|
+
if (tool == null) {
|
|
76
|
+
throw new Error('Cline did not create its native skills tool.');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { signature, tool };
|
|
33
80
|
}
|
|
34
81
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
82
|
+
function projectClineSkills({
|
|
83
|
+
skills,
|
|
84
|
+
}: {
|
|
85
|
+
skills: ReadonlyArray<HarnessV1Skill>;
|
|
86
|
+
}): ReadonlyArray<ProjectedClineSkill> {
|
|
87
|
+
const names = new Set<string>();
|
|
88
|
+
const ids = new Set<string>();
|
|
89
|
+
return skills
|
|
90
|
+
.map(skill => {
|
|
91
|
+
if (
|
|
92
|
+
!CLINE_SKILL_NAME_PATTERN.test(skill.name) ||
|
|
93
|
+
skill.name === '.' ||
|
|
94
|
+
skill.name === '..'
|
|
95
|
+
) {
|
|
96
|
+
throw new Error(`Invalid Cline skill name: ${skill.name}`);
|
|
97
|
+
}
|
|
98
|
+
if (names.has(skill.name)) {
|
|
99
|
+
throw new Error(`Duplicate Cline skill name: ${skill.name}`);
|
|
100
|
+
}
|
|
101
|
+
names.add(skill.name);
|
|
102
|
+
|
|
103
|
+
const id = normalizeSkillToken(skill.name);
|
|
104
|
+
if (ids.has(id)) {
|
|
105
|
+
throw new Error(`Duplicate Cline skill identifier: ${id}`);
|
|
106
|
+
}
|
|
107
|
+
ids.add(id);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
id,
|
|
111
|
+
name: skill.name,
|
|
112
|
+
description: skill.description,
|
|
113
|
+
content: skill.content,
|
|
114
|
+
files: (skill.files ?? [])
|
|
115
|
+
.map(file => ({
|
|
116
|
+
path: normalizeSkillFilePath({
|
|
117
|
+
skillName: skill.name,
|
|
118
|
+
filePath: file.path,
|
|
119
|
+
}),
|
|
120
|
+
content: file.content,
|
|
121
|
+
}))
|
|
122
|
+
.sort(
|
|
123
|
+
(left, right) =>
|
|
124
|
+
left.path.localeCompare(right.path) ||
|
|
125
|
+
left.content.localeCompare(right.content),
|
|
126
|
+
),
|
|
127
|
+
};
|
|
128
|
+
})
|
|
129
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeSkillToken(skill: string): string {
|
|
133
|
+
return skill.trim().replace(/^\/+/, '').toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeSkillFilePath({
|
|
137
|
+
skillName,
|
|
138
|
+
filePath,
|
|
139
|
+
}: {
|
|
140
|
+
skillName: string;
|
|
141
|
+
filePath: string;
|
|
142
|
+
}): string {
|
|
143
|
+
const normalized = path.posix.normalize(filePath);
|
|
144
|
+
if (
|
|
145
|
+
normalized === '' ||
|
|
146
|
+
normalized === '.' ||
|
|
147
|
+
normalized.startsWith('../') ||
|
|
148
|
+
normalized.includes('/../') ||
|
|
149
|
+
normalized.endsWith('/..') ||
|
|
150
|
+
path.posix.isAbsolute(normalized)
|
|
151
|
+
) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Invalid Cline skill file path for ${skillName}: ${filePath}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return normalized;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function renderSkillInstructions({
|
|
160
|
+
skill,
|
|
161
|
+
args,
|
|
162
|
+
}: {
|
|
163
|
+
skill: ProjectedClineSkill;
|
|
164
|
+
args: string | undefined;
|
|
165
|
+
}): string {
|
|
166
|
+
const trimmedArgs = args?.trim();
|
|
167
|
+
const argsTag = trimmedArgs
|
|
168
|
+
? `\n<command-args>${trimmedArgs}</command-args>`
|
|
169
|
+
: '';
|
|
170
|
+
const description = skill.description.trim()
|
|
171
|
+
? `Description: ${skill.description.trim()}\n\n`
|
|
172
|
+
: '';
|
|
173
|
+
const files =
|
|
174
|
+
skill.files.length === 0
|
|
175
|
+
? ''
|
|
176
|
+
: `\n\n<skill-files>\n${skill.files
|
|
177
|
+
.map(
|
|
178
|
+
file =>
|
|
179
|
+
`<skill-file path=${JSON.stringify(file.path)}>\n${file.content}\n</skill-file>`,
|
|
180
|
+
)
|
|
181
|
+
.join('\n')}\n</skill-files>`;
|
|
182
|
+
|
|
183
|
+
return `<command-name>${skill.name}</command-name>${argsTag}\n<command-instructions>\n${description}${skill.content}${files}\n</command-instructions>`;
|
|
59
184
|
}
|
package/src/cline-tools.ts
CHANGED
|
@@ -24,6 +24,7 @@ export const CLINE_NATIVE_BUILTIN_NAMES = [
|
|
|
24
24
|
'grep',
|
|
25
25
|
'glob',
|
|
26
26
|
'ls',
|
|
27
|
+
'skills',
|
|
27
28
|
] as const;
|
|
28
29
|
|
|
29
30
|
export type ClineNativeBuiltinName =
|
|
@@ -39,6 +40,7 @@ export const CLINE_NATIVE_TOOL_KINDS: Readonly<
|
|
|
39
40
|
grep: 'readonly',
|
|
40
41
|
glob: 'readonly',
|
|
41
42
|
ls: 'readonly',
|
|
43
|
+
skills: 'readonly',
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
export function isClineBuiltinToolName(
|
|
@@ -127,9 +129,9 @@ async function guarded<T>(run: () => Promise<T>): Promise<ClineToolResult> {
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
/**
|
|
130
|
-
* Build the sandbox-backed
|
|
131
|
-
*
|
|
132
|
-
* (cline-harness.ts)
|
|
132
|
+
* Build the sandbox-backed coding tools handed to the Cline runtime. These
|
|
133
|
+
* schemas mirror their declarations in `CLINE_BUILTIN_TOOLS`
|
|
134
|
+
* (cline-harness.ts). The in-host skills tool is built separately.
|
|
133
135
|
*/
|
|
134
136
|
export function buildBuiltinAgentTools({
|
|
135
137
|
ops,
|
package/src/index.ts
CHANGED
|
@@ -10,4 +10,4 @@ export const cline = createCline();
|
|
|
10
10
|
export { createCline } from './cline-harness';
|
|
11
11
|
export { VERSION } from './version';
|
|
12
12
|
export type { ClineHarnessSettings } from './cline-harness';
|
|
13
|
-
export type { ClineAuthenticationMode
|
|
13
|
+
export type { ClineAuthenticationMode } from './cline-auth';
|