mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-19 14:07:36 +02:00
fix: mask stored credentials and validate agent saves
This commit is contained in:
1 parent
4a5b177947
commit
76da98944c
18 files changed
+780
-22
No files matched your search
@@ -3495,6 +3495,7 @@
|
||||
"newFilePathInvalid": "Invalid path (no .. or absolute paths)",
|
||||
"noPackageFiles": "No files listed",
|
||||
"unsavedSwitch": "You have unsaved changes. Switch file anyway?",
|
||||
"saveDescriptionFirst": "Save the description in SKILL.md before switching to another file",
|
||||
"contentLabel": "Content",
|
||||
"contentPlaceholder": "Edit the selected file…",
|
||||
"contentPlaceholderAdd": "SKILL.md body only (front matter is generated)…",
|
||||
|
||||
@@ -3508,6 +3508,7 @@
|
||||
"newFilePathInvalid": "Недопустимый путь (без .. и абсолютных путей)",
|
||||
"noPackageFiles": "Файлы не указаны",
|
||||
"unsavedSwitch": "Есть несохранённые изменения. Всё равно переключить файл?",
|
||||
"saveDescriptionFirst": "Сохраните описание в SKILL.md перед переходом к другому файлу",
|
||||
"contentLabel": "Содержимое",
|
||||
"contentPlaceholder": "Измените выбранный файл…",
|
||||
"contentPlaceholderAdd": "Только тело SKILL.md (front matter создаётся автоматически)…",
|
||||
|
||||
@@ -3483,6 +3483,7 @@
|
||||
"newFilePathInvalid": "路径无效(禁止 .. 与绝对路径)",
|
||||
"noPackageFiles": "未列出文件",
|
||||
"unsavedSwitch": "当前文件有未保存修改,确定切换?",
|
||||
"saveDescriptionFirst": "请先在 SKILL.md 中保存描述,再切换到辅助文件",
|
||||
"contentLabel": "内容",
|
||||
"contentPlaceholder": "编辑当前选中的文件…",
|
||||
"contentPlaceholderAdd": "SKILL.md 正文(无需手写 YAML 头)…",
|
||||
|
||||
@@ -3199,6 +3199,7 @@ async function probeSelectedAIChannels() {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
provider: ch.provider || 'openai_compatible',
|
||||
channel_id: id,
|
||||
base_url: ch.base_url || '',
|
||||
api_key: ch.api_key || '',
|
||||
model: ch.model || ''
|
||||
@@ -3527,6 +3528,12 @@ function populateModelSelect(scope, models, currentValue) {
|
||||
async function fetchModelList(scope) {
|
||||
const tFn = typeof window.t === 'function' ? window.t : (k) => k;
|
||||
const creds = resolveModelListCredentials(scope);
|
||||
creds.credential_scope = scope;
|
||||
if (scope === 'openai') creds.channel_id = selectedAIChannelId;
|
||||
const keyInputByScope = {vision: 'vision-api-key', hitlAudit: 'hitl-audit-model-api-key', knowledgeEmbedding: 'knowledge-embedding-api-key'};
|
||||
if (keyInputByScope[scope] && !document.getElementById(keyInputByScope[scope])?.value.trim()) {
|
||||
creds.channel_id = selectedAIChannelId;
|
||||
}
|
||||
const modelListUiIds = {
|
||||
openai: {
|
||||
btnId: 'fetch-openai-models-btn',
|
||||
@@ -3641,7 +3648,7 @@ async function testVisionConnection() {
|
||||
const response = await apiFetch('/api/config/test-vision', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ vision: vision, openai: openai })
|
||||
body: JSON.stringify({ vision: vision, openai: openai, channel_id: selectedAIChannelId })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
@@ -3763,6 +3770,8 @@ async function testHitlAuditModelConnection() {
|
||||
const payload = typeSafe
|
||||
? { base_url: baseUrl, api_key: apiKey, model: model }
|
||||
: cfg;
|
||||
payload.credential_scope = 'hitlAudit';
|
||||
if (!document.getElementById('hitl-audit-model-api-key')?.value.trim()) payload.channel_id = selectedAIChannelId;
|
||||
const response = await apiFetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -3886,6 +3895,7 @@ async function testOpenAIConnection() {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
provider: provider,
|
||||
channel_id: channelId,
|
||||
base_url: baseUrl,
|
||||
api_key: apiKey,
|
||||
model: model
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const source = fs.readFileSync('web/static/js/skills.js', 'utf8');
|
||||
const start = source.indexOf('async function selectSkillPackageFile(');
|
||||
const end = source.indexOf('// 编辑skill', start);
|
||||
function harness(dirty) {
|
||||
const inputs = {'skill-description': {disabled:false}, 'skill-content': {value:''}};
|
||||
let calls = 0;
|
||||
const ctx = {
|
||||
skillDescriptionDirty:dirty, skillFileDirty:false, skillActivePath:'SKILL.md',
|
||||
document:{getElementById:id => inputs[id] || null},
|
||||
_t:x => x, showNotification() {}, renderSkillPackageTree() {}, confirm:() => true,
|
||||
apiFetch:async () => {calls++; return {ok:true,json:async () => ({content:'text',skill:{content:'body'}})};},
|
||||
};
|
||||
vm.createContext(ctx);
|
||||
vm.runInContext(source.slice(start,end),ctx);
|
||||
return {ctx,inputs,calls:() => calls};
|
||||
}
|
||||
test('auxiliary file editing disables description and returning to SKILL.md enables it', async () => {
|
||||
const {ctx,inputs} = harness(false);
|
||||
await ctx.selectSkillPackageFile('test','references/example.md',{});
|
||||
assert.equal(inputs['skill-description'].disabled,true);
|
||||
await ctx.selectSkillPackageFile('test','SKILL.md',{});
|
||||
assert.equal(inputs['skill-description'].disabled,false);
|
||||
});
|
||||
test('unsaved description cannot disappear when switching files', async () => {
|
||||
const h = harness(true);
|
||||
await h.ctx.selectSkillPackageFile('test','references/example.md',{});
|
||||
assert.equal(h.ctx.skillActivePath,'SKILL.md');
|
||||
assert.equal(h.calls(),0);
|
||||
});
|
||||
@@ -7,6 +7,7 @@ let currentEditingSkillName = null;
|
||||
let skillModalAddMode = true;
|
||||
let skillActivePath = 'SKILL.md';
|
||||
let skillFileDirty = false;
|
||||
let skillDescriptionDirty = false;
|
||||
let skillPackageFiles = [];
|
||||
let skillModalControlsWired = false;
|
||||
let isSavingSkill = false; // 防止重复提交
|
||||
@@ -454,6 +455,8 @@ function wireSkillModalOnce() {
|
||||
skillModalControlsWired = true;
|
||||
const addTa = document.getElementById('skill-content-add');
|
||||
const edTa = document.getElementById('skill-content');
|
||||
const descriptionInput = document.getElementById('skill-description');
|
||||
if (descriptionInput) descriptionInput.addEventListener('input', () => { skillDescriptionDirty = true; });
|
||||
if (addTa) addTa.addEventListener('input', () => { if (skillModalAddMode) skillFileDirty = true; });
|
||||
if (edTa) edTa.addEventListener('input', () => { if (!skillModalAddMode) skillFileDirty = true; });
|
||||
const nb = document.getElementById('skill-new-file-btn');
|
||||
@@ -483,6 +486,8 @@ function showAddSkillModal() {
|
||||
if (!modal) return;
|
||||
|
||||
skillModalAddMode = true;
|
||||
skillDescriptionDirty = false;
|
||||
document.getElementById('skill-description').disabled = false;
|
||||
skillFileDirty = false;
|
||||
skillActivePath = 'SKILL.md';
|
||||
skillPackageFiles = [];
|
||||
@@ -544,12 +549,18 @@ async function selectSkillPackageFile(skillId, path, opts) {
|
||||
const freshContent = opts && Object.prototype.hasOwnProperty.call(opts, 'freshContent')
|
||||
? opts.freshContent
|
||||
: null;
|
||||
if (path !== 'SKILL.md' && skillDescriptionDirty) {
|
||||
showNotification(_t('skillModal.saveDescriptionFirst'), 'error');
|
||||
return;
|
||||
}
|
||||
if (!force && skillFileDirty) {
|
||||
if (!confirm(_t('skillModal.unsavedSwitch'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
skillActivePath = path;
|
||||
const descriptionInput = document.getElementById('skill-description');
|
||||
if (descriptionInput) descriptionInput.disabled = path !== 'SKILL.md';
|
||||
const label = document.getElementById('skill-active-path');
|
||||
if (label) label.textContent = path;
|
||||
const hint = document.getElementById('skill-body-hint-edit');
|
||||
@@ -591,6 +602,8 @@ async function editSkill(skillId) {
|
||||
const modal = document.getElementById('skill-modal');
|
||||
if (!modal) return;
|
||||
skillModalAddMode = false;
|
||||
skillDescriptionDirty = false;
|
||||
document.getElementById('skill-description').disabled = false;
|
||||
skillFileDirty = false;
|
||||
skillActivePath = 'SKILL.md';
|
||||
const pkg = document.getElementById('skill-package-editor');
|
||||
@@ -739,6 +752,8 @@ function closeSkillModal() {
|
||||
currentEditingSkillName = null;
|
||||
skillModalAddMode = true;
|
||||
skillFileDirty = false;
|
||||
skillDescriptionDirty = false;
|
||||
document.getElementById('skill-description').disabled = false;
|
||||
skillPackageFiles = [];
|
||||
skillActivePath = 'SKILL.md';
|
||||
}
|
||||
@@ -833,6 +848,7 @@ async function saveSkill() {
|
||||
}
|
||||
|
||||
skillFileDirty = false;
|
||||
if (path === 'SKILL.md') skillDescriptionDirty = false;
|
||||
showNotification(_t('skills.saveSuccess'), 'success');
|
||||
const filesRes = await apiFetch(`/api/skills/${encodeURIComponent(currentEditingSkillName)}/files`);
|
||||
if (filesRes.ok) {
|
||||
|
||||
@@ -1244,7 +1244,8 @@ function probeWebshellConnection(conn) {
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
connection_id: conn.id || '',
|
||||
command: buildWebshellProbeCommand(probeToken)
|
||||
command: buildWebshellProbeCommand(probeToken),
|
||||
connection_id: password === '********' ? (document.getElementById('webshell-edit-id')?.value || '') : ''
|
||||
})
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
|
||||
Reference in new issue
Block a user